Often, when callable classes are implemented, the descriptors are forgotten. Consider some decorator:
class X:
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
print(self.f(*args, **kwargs))
This works fine when applied to a function:
@X
def g(blah):
print(blah)
g(1) # prints 1
But if you apply it to a method, you get
class Z:
@X
def f(self, blah):
print(blah)
z = Z()
z.f(1) # TypeError: Z.f() missing 1 required positional argument: 'blah'
To make it easier to implement decorators that work on both functions and methods, I suggest adding the mixin functools.OrdinaryCallable
:
class OrdinaryCallable(Generic[P, R_co]):
@abc.abstractmethod
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R_co:
raise NotImplementedError
@overload
def __get__(self, instance: None, owner: Any = None) -> OrdinaryCallable[P, R_co]:
...
@overload
def __get__(self, instance: U, owner: Any = None
) -> Callable[..., R_co]: # No perfect type annotation is possible here yet, but see https://github.com/python/typing/issues/1372
...
def __get__(self, instance: Any, owner: Any = None) -> Callable[..., R_co]:
if instance is None:
return self
return partial(self, instance)
This provides both the (hopefully correct) behavior and the correct type annotation for an ordinary callable function/method.
As far as I know, type checkers don’t use the descriptor protocol yet, but these annotations would come in handy if they were to start using it. Even if they continue to special-case method lookup, then having such a base class in the standard library would allow them to treat classes that incorporate this mixin the same way they treat ordinary callables.
Of significantly lesser benefit as mixins, but of similar utility as type annotation providers might be StaticMethod
and ClassMethod
base classes (described here Add PartialApplication · Issue #1372 · python/typing · GitHub). But I’m not proposing those yet.