Reputation: 1112
I always though that Callable is equivalent to having the dunder __call__
but apparently there is also __name__
, because the following code is correct for mypy --strict
:
def print_name(f: Callable[..., Any]) -> None:
print(f.__name__)
def foo() -> None:
pass
print_name(foo)
print_name(lambda x: x)
What is actual interface of python Callable?
I dug out what functools.wraps
does. AFAIU it sets ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
- is that the same what the Callable
is expected to have?
Upvotes: 6
Views: 808
Reputation: 18508
So the mypy
position up until now seems to have been that most of the time, when a variable is annotated with Callable
, the user expects it to stand for a user-defined function (i.e. def something(...): ...
).
Even though user-defined functions are technically a subtype of the callable and even though they are the ones that define a number of those attributes you mentioned, some users are not aware of this distinction and would be surprised, if mypy
raised an error with code like this:
from collections.abc import Callable
from typing import Any
def f(cal: Callable[..., Any]) -> None:
print(cal.__name__)
print(cal.__globals__)
print(cal.__kwdefaults__)
print(cal.foo)
Each of those print
-lines should be an error, yet only the last actually triggers one.
Moreover, if we define a minimal callable class that doesn't have those attributes, it is treated as a subtype of Callable
by both Python and mypy
, creating a logical contradication:
class Bar:
def __call__(self) -> None:
print(f"hi mom")
f(Bar()) # this is valid
print(Bar().__name__) # this is an error
Their argument so far amounts to maintaining convenience for users that have so far failed to see the distinction between callable subtypes, and by extension avoiding confused issues being opened by those users, asking why callables shouldn't have __name__
or those other attributes. (I hope I am being charitable enough with my interpretation.)
I find this to be a very odd position (to put it mildly) and I expressed as much in the issue I opened for this. I'll keep this answer updated, if any new insights are reached in the discussion around the issue.
Bottom line is: You are right, callables must have the __call__
method and do not require anything else.
Upvotes: 4