Repository navigation
classmethod typing is weird #10396
Copy link
Copy link
Closed
Description
It feels like the classmethod typing is slightly wrong.
Typeshed defines it as:
class classmethod(Generic[_T, _P, _R_co]):
@property
def __isabstractmethod__(self) -> bool: ...
def __init__(self, __f: Callable[Concatenate[_T, _P], _R_co]) -> None: ...
def __get__(self, __instance: _T, __owner: type[_T] | None = None) -> Callable[_P, _R_co]: ...
...With mypy the following program fails:
from typing import *
def foo(cls: Type['G'], x: int) -> str: return ""
def bar(cls: 'G' | None, x: int) -> str: return ""
class G:
x = classmethod(foo)
y = classmethod(bar)
G.x(1) # Fails
G.y(1) # Works new.py:11: error: Argument 1 to "__get__" of "classmethod" has incompatible type "None"; expected "type[G]" [arg-type]
new.py:11: error: Argument 2 to "__get__" of "classmethod" has incompatible type "type[G]"; expected "type[type[G]] | None" [arg-type]
The proper solution would probably be to type __init__ as:
def __init__(self, __f: Callable[Concatenate[Type[_T], _P], _R_co]) -> None: ... Would such a pull request be accepted? If I change this in Mypy it fails with error: Need type annotation for "x", which I think is much better (and Mypy could probably still infer the type variable _T properly in the future). What do you think?
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
AlexWaygood commentedon Jul 2, 2023
I think you may have a typo in your message here — this is exactly the signature we currently have for
classmethod.__init__davidhalter commentedon Jul 2, 2023
Thanks Alex. I changed it to
Type[T]above. Sorry, this was was obviously the most important part 😕AlexWaygood commentedon Jul 2, 2023
Yes, I think you're right about the
__init__method. Good catch!The
__get__method looks off as well. Here's the behaviour you get on CPython:That implies the
__init__and__get__methods should together be:Feel free to make a PR :)
davidhalter commentedon Jul 3, 2023
Makes sense. But shouldn't it be:
I feel like that's closer to the descriptor protocol. I know that it's probably possible to pass anything for
__instancebut it feels weird to allow that, when descriptors would never do that normally (and we should probably reject weird inputs).AlexWaygood commentedon Jul 3, 2023
Yes, you're right, the
objectannotation I suggested in the first overload is probably taking it a bit too far. Maybe something like this:davidhalter commentedon Jul 20, 2023
Thanks. Sorry I was a bit slower, but it was definitely on my todo list!