Skip to content

Infer the 'cls' argument of a classmethod as the defining class, not Any #292

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
JukkaL opened this issue Jul 15, 2014 · 8 comments
Closed
Assignees
Labels

Comments

@JukkaL
Copy link
Collaborator

JukkaL commented Jul 15, 2014

Add support for classmethod. Currently we support staticmethod.

@JukkaL
Copy link
Collaborator Author

JukkaL commented Jul 24, 2014

Partial support implemented in db9efde by Jeff Walden.

@mrwright
Copy link
Contributor

For reference, here's an example of code that incorrectly typechecks because of this:

class C(object):
    @classmethod
    def f(cls, a, b):
        # type: (int, int) -> None
        pass

    @classmethod
    def g(cls):
        # type: () -> None
        cls.f(2, 3, 4)

Replacing cls.f with C.f correctly produces a Too many arguments for "f" of "C" error. This situation came up in code I was annotating.

@JukkaL
Copy link
Collaborator Author

JukkaL commented Dec 28, 2015

The type of cls in currently implicitly Any, which is way too general. The reason why this hasn't been implemented yet is that the type of cls is actually quite subtle with respect to __init__.

Consider this example:

class A:
    def __init__(self, x: int) -> None: ...

    @classmethod
    def f(cls) -> None:
        a = cls(1)
        ...

class B(A):
    def __init__(self, x: int, y: str) -> None: ...

B.f()  # runtime error

To catch the above error, we have at least two options:

  1. Reject calls to cls. This would be too restrictive but pretty straightforward to implement. Instead of calling cls, you could implement a factory class method and call that, but that would be less convenient.
  2. Somehow prevent overriding __init__ with an incompatible signature in some cases such as B above. Usually we should allow these overrides.

We could also decide to not care about this and allow calling cls with arbitrary arguments (type would be something like Callable[..., A]).

For 2, we could provide an annotation that enforces this. Here are some potential ways to do it:

  • Class decorator (for A in the above example)
  • Method decorator (for A.__init__)
  • Magic comment such as # mypy: compatible-init-override within class body or within __init__

The same issue also happens with __new__.

There are other related things around this such as 'self types' but they aren't immediately relevant to this issue.

@gvanrossum
Copy link
Member

gvanrossum commented Jan 9, 2016 via email

@JukkaL
Copy link
Collaborator Author

JukkaL commented Jan 9, 2016

Sounds pretty reasonable. We figure out the details of stage 2 later on; stage 1 is simple to implement.

@JukkaL JukkaL added this to the 0.3.2 milestone Mar 10, 2016
@JukkaL
Copy link
Collaborator Author

JukkaL commented Mar 10, 2016

Looking for cls() is not good enough for stubs, as they have empty method bodies. We could use an __init__ decorator to mark that subclasses should use a compatible signature.

@gvanrossum
Copy link
Member

Honestly I've always found cls() of dubious correctness -- similar to self.__class__(). But yes, people do use it. :-(

@gnprice
Copy link
Collaborator

gnprice commented Mar 10, 2016

For the thread: we've heard this request several times now at Dropbox, where we have a lot of classmethods. One place we heard it was on our recent survey of internal users.

@gvanrossum gvanrossum changed the title Support classmethod Infer the 'cls' argument of a classmethod as the defining class, not Any Mar 17, 2016
@gvanrossum gvanrossum self-assigned this Mar 18, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants