mypy has the binding semantics for @classmethod baked into it (in fact from looking at the source code I believe it is hard-coded for builtins.classmethod) so this works:
from __future__ import annotations
from typing import Any
class Foo:
def __init__(self, x: Any) -> None:
self.x = x
@classmethod
def create(cls, x: Any) -> Foo:
return cls(x)
if __name__ == '__main__':
foo = Foo.create(1)
print(foo)
$ mypy a0.py
Success: no issues found in 1 source file
But say, for valid reasons, I have a decorator that has classmethod-like binding behavior on the first argument (expecting it to be a class). To give a simple example that approximates classmethod itself:
class myclassmethod:
def __init__(self, func):
self.func = func
def __get__(self, obj, cls):
return partial(self.func, cls)
class Foo:
def __init__(self, x: Any) -> None:
self.x = x
@myclassmethod
def create(cls, x: Any) -> Foo:
return cls(x)
Unsurprisingly this does not check out; mypy assumes the first argument is bound to a Foo instance, and errors out at the call site of cls(x):
$ mypy a1.py
a1.py:20: error: "Foo" not callable
If I try to explicitly give the "cls" a type in contradicts the default bind_self behavior for instance methods:
class Foo:
def __init__(self, x: Any) -> None:
self.x = x
@myclassmethod
def create(cls: Type[Foo], x: Any) -> Foo:
return cls(x)
$ mypy a2.py
a2.py:19: error: The erased type of self "Type[a2.Foo]" is not a supertype of its class "a2.Foo"
The only workaround I've found is to not declare the type of cls in the signature, but instead to cast it at the call site:
class Foo:
def __init__(self, x: Any) -> None:
self.x = x
@myclassmethod
def create(cls, x: Any) -> Foo:
return cast(Type[Foo], cls)(x)
This passes as far as type checking goes, but is a little inconvenient and error-prone since this will have to be done by any user of @myclassmethod.
I see that there is a is_classmethod argument to mypy's bind_self, but this is really only applied in the specific case of functions decorated with builtins.classmethod. Subclassing classmethod doesn't help either (I guess mypy is making the conservative assumption that a subclass of classmethod may have arbitrary behavior).
I just wonder if there's a way, without digging deep into mypy internals, to explicitly override the self-binding behavior of functions that are arguments to a decorator.