# Feature proposal: a new classmethod decorator that only allows calling from the class type

**URL:** <https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984>\
**Category:** Ideas\
**Tags:** typing\
**Created:** [September 5, 2023, 5:52am UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984 "2023-09-05T05:52:09Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![kwsp](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kwsp/32/6624_2.png) [@kwsp](https://discuss.python.org/u/kwsp)\
**Post date:** [September 5, 2023, 5:52am UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/1 "2023-09-05T05:52:10Z")

</div>

This sort of solves an XY problem:

- Problem X: In python there is no way to enforce that the return value of a method is not discarded, like C++'s `[[nodiscard]]`
- Problem Y: a popular paradigm in Python is to use `@classmethod` to define an alternative constructor. However, sometimes it’s hard to infer from the method name if such a method is a classmethod, or if it modifies an object in place with the new parameters. Often, users call the classmethod thinking it modifies the calling object in place. For example, deep learning libraries get user issues where they try to load weights for a model, not knowing they called a classmethod, leaving them confused about why their model isn’t in a desired state after calling the classmethod. There’s no mechanism to easily emit a warning or raise an exception to improve usability.

I proposed a solution: for certain classmethod, we raise an exception if the classmethod is called on an instance as a proxy to catch these usage errors.

A pure python implementation:

```auto
from typing import TYPE_CHECKING
from types import MethodType

class _restricted_classmethod:
    """
    Custom `classmethod` that raises a TypeError when the decorated method
    is called on an instance instead of the class type.
    """

    def __init__ (self, method):
        self.method = method

    def __get__ (self, instance, cls):
        if instance is not None:
            raise TypeError(
                f"The classmethod `{cls. __name__ }.{self.method. __name__ }` cannot be called on an instance. "
                f"Please call it on the class type and make sure the return value is used."
            )
        return MethodType(self.method, cls)

# currently all static type checkers (pyright, mypy, ...) hard codes `@classmethod` and `@staticmethod`,
# so even with the above decorator, the first argument to the method would be inferred as `Self@T`
# and not `type[T]`, so this is only to make the static type checker happy.
restricted_classmethod = classmethod if TYPE_CHECKING else _restricted_classmethod

class Demo:
    @restricted_classmethod
    def rcmethod(cls):
        pass

Demo.rcmethod() # OK
Demo().rcmethod() # Runtime TypeError

```

Some discussion on type checking the above impl: [Inference of `cls` type on a custom `classmethod` · Issue #5865 · microsoft/pyright · GitHub](https://github.com/microsoft/pyright/issues/5865#issuecomment-1705844001)

Obviously I would love for there to be an equivalent to [[nodiscard]] in Python. I’d love to hear suggestions on how this could potentially be implemented in static type checking too.

Some example issues:

- [`load_from_checkpoint` should not be called as an instance method · Issue #18169 · Lightning-AI/lightning · GitHub](https://github.com/Lightning-AI/lightning/issues/18169)
- [Problems with reload the model weights only ( `pl.LightningModule.load_from_checkpoint` method） · Issue #17356 · Lightning-AI/lightning · GitHub](https://github.com/Lightning-AI/lightning/issues/17356)

---

<div class="post-metadata">

**Author:** ![ajoino](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ajoino/32/6907_2.png) [@ajoino](https://discuss.python.org/u/ajoino)\
**Post date:** [September 5, 2023, 8:11am UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/2 "2023-09-05T08:11:01Z")

</div>

To me, it sounds like the real issue here is the (relatively) novice userbase of pytorch/pytorch lightning. I bet that many users first introduction to Python is through these ML libraries and frameworks. They likely copy and paste a lot and recognize simple patterns that they try to reuse. Classmethods is one of those cases where some simple patterns break. Instead of adding safety rails, this should be considered a learning opportunity for the novice user.

For advanced users that know how to read documentation and code, this is a non issue IMO. Furthermore, it seems Pytorch Lightning uses especially confusing names, which could even trip up more experienced users. Using the same prefix `load_from_*` for both class and instance methods, instead of the more standard `from_*` for classmethods is unforturnate design, but not something that requires support from the language IMO.

---

<div class="post-metadata">

**Author:** ![JamesParrott](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jamesparrott/32/10534_2.png) [@JamesParrott](https://discuss.python.org/u/JamesParrott)\
**Post date:** [September 5, 2023, 10:57am UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/3 "2023-09-05T10:57:51Z")

</div>

Thanks for the code snippet. It’s more of a fix with dynamic code than stopping the issue early with a type checker, but can’t the method be removed from an instance, or at least set to None, in its ` __init__ ` call ? A restricted class method that creates an instance, could even remove itself from that instance before returning it.

Anyway, why don’t you call it “constructor” instead of “restricted class method”, or is this desirable for other uses than stopping mistaken calls to constructors? If a callable attribute on a class can’t be called on an instance, then is it really still a method at all?

---

<div class="post-metadata">

**Author:** ![layday](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/layday/32/2771_2.png) [@layday](https://discuss.python.org/u/layday)\
**Post date:** [September 5, 2023, 11:00am UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/4 "2023-09-05T11:00:49Z")

</div>

You can define the class method on the metaclass so that it is not available on instances of the class, if that’s something you haven’t considered.

---

<div class="post-metadata">

**Author:** ![thejcannon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/thejcannon/32/23759_2.png) [@thejcannon](https://discuss.python.org/u/thejcannon)\
**Post date:** [September 5, 2023, 1:36pm UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/5 "2023-09-05T13:36:26Z")

</div>

`classmethod` is defined using descriptors. You can also define an equivalent descriptor for a `classmethod` where ` __get__ ` rejects instance calling

---

<div class="post-metadata">

**Author:** ![kwsp](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kwsp/32/6624_2.png) [@kwsp](https://discuss.python.org/u/kwsp)\
**Post date:** [September 5, 2023, 5:06pm UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/6 "2023-09-05T17:06:06Z")

</div>

I agree its a novice problem. The raised exception should make it easier for people to learn.

---

<div class="post-metadata">

**Author:** ![kwsp](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kwsp/32/6624_2.png) [@kwsp](https://discuss.python.org/u/kwsp)\
**Post date:** [September 5, 2023, 5:09pm UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/7 "2023-09-05T17:09:51Z")

</div>

- IMO removing the method from the instance is more confusing than raising an exception, since its hides more from the user and is unexpected, and we can’t raise a more user friendly exception/warning.
- I don’t think calling it “constructor” is better, since the method doesn’t necessarily have to return an instance of `cls`.
- You’re right - if it can’t be called on an instance, it’s weird to call it a method. Again, this is the Y problem. The X problem IMO is that Python doesn’t have `[[nodiscard]]`

---

<div class="post-metadata">

**Author:** ![kwsp](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kwsp/32/6624_2.png) [@kwsp](https://discuss.python.org/u/kwsp)\
**Post date:** [September 5, 2023, 5:11pm UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/8 "2023-09-05T17:11:06Z")

</div>

Good suggestion - I didn’t know this! Removing the method on the instance also removes the opportunity to raise a user friendly exception/warning though.

---

<div class="post-metadata">

**Author:** ![kwsp](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kwsp/32/6624_2.png) [@kwsp](https://discuss.python.org/u/kwsp)\
**Post date:** [September 5, 2023, 5:11pm UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/9 "2023-09-05T17:11:41Z")

</div>

If you read my original post thoroughly, you’ll notice that’s exactly what I used to implement the pure python example.

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [September 5, 2023, 6:32pm UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/10 "2023-09-05T18:32:28Z")

</div>

Have you considered proposing the addition of a `typing.no_discard` decorator? Then type checkers (like MyPy) or linters (like Ruff) could produce appropriate warnings?

---

<div class="post-metadata">

**Author:** ![jamestwebber](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jamestwebber/32/12799_2.png) [@jamestwebber](https://discuss.python.org/u/jamestwebber)\
**Post date:** [September 5, 2023, 6:38pm UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/11 "2023-09-05T18:38:37Z")

</div>

> [@kwsp](#):
>
> Good suggestion - I didn’t know this! Removing the method on the instance also removes the opportunity to raise a user friendly exception/warning though.

It would remove the method from an instance’s auto-complete in an IDE, though, so the code doesn’t get written in the first place (most of the time). That seems even more desirable than a helpful exception message.

---

<div class="post-metadata">

**Author:** ![JamesParrott](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jamesparrott/32/10534_2.png) [@JamesParrott](https://discuss.python.org/u/JamesParrott)\
**Post date:** [September 5, 2023, 7:06pm UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/12 "2023-09-05T19:06:51Z")

</div>

Sure. Replacing it with a dummy method that raises an informative error would be a more helpful UX. But you get the idea.

---

<div class="post-metadata">

**Author:** ![ntessore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ntessore/32/10786_2.png) [@ntessore](https://discuss.python.org/u/ntessore)\
**Post date:** [September 7, 2023, 8:24pm UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/13 "2023-09-07T20:24:06Z")

</div>

Alternatively, a `strict` keyword parameter on `classmethod` might be nice. I would probably use that more often than not.

```py
class A:
    @classmethod(strict=True)
    def from_frob(cls):
        ...

```

---

<div class="post-metadata">

**Author:** ![kkpattern](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kkpattern/32/9704_2.png) [@kkpattern](https://discuss.python.org/u/kkpattern)\
**Post date:** [September 8, 2023, 4:01am UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/14 "2023-09-08T04:01:07Z")

</div>

For `[[nodiscard]]` I think we think utilize `typing.Annoated` like this:

```python
from typing import Annotated

def return_some_value() -> Annotated[int, "nodiscard"]:
    pass

```

Then maybe we can write a mypy plugin to check if the return value of `return_some_value` is discarded or not?

---

<div class="post-metadata">

**Author:** ![ajoino](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ajoino/32/6907_2.png) [@ajoino](https://discuss.python.org/u/ajoino)\
**Post date:** [September 8, 2023, 4:46am UTC](https://discuss.python.org/t/feature-proposal-a-new-classmethod-decorator-that-only-allows-calling-from-the-class-type/32984/15 "2023-09-08T04:46:39Z")

</div>

If something like this were to be implemented, I’d prefer an specialized `NoDiscard` annotation instead of using the generic `Annotation`. But, I’d much rather prefer not to have this at all, because I don’t think it’s the responsibility of the callee to tell the caller what it can and cannot do with the value produced.

Also, what would the semantics be? Would the following snippet be ok?

```python
def add(a: int, b: int) -> NoDiscard[int]:
    return a + b

c = add(1, 3) # the value is assigned but unused later, should the type checker warn?

print("I did a thing!")

```

I’d much rather let this be a learning opportunity for the new Python user than add a change to the language that doesn’t let me call functions the way I want to.
