-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Why is Callable
assumed to be a user defined function?
#14392
Comments
I think the main reason is that if we don't do this, we'll get a lot of false positives from people who use It does, as you say, create a minor hole in the type system. To fix it without creating unnecessary pain for users, we probably need some new type system features. For example, intersection types (python/typing#213) might allow writing a type like |
Note that if you want a type that doesn't assume all the attributes you get on standard function objects, you can just use a callback protocol: from typing import Any, Protocol
class SaferCallable(Protocol):
def __call__(self, *args, **kwargs) -> Any: ...
def f(call: SaferCallable) -> None:
print(call.__name__) # error: "SaferCallable" has no attribute "__name__" [attr-defined] |
@AlexWaygood I realize that I can define any custom protocol myself. I did not ask for workarounds or usage suggestions here. My contention is that this should not be necessary, if @JelleZijlstra Thank you for responding.
I guess I have a different definition of what "false positive" means. If someone wrongly assumes a from typing import Any, Protocol
class CallableWithNameAndFoo(Protocol):
__name__: str
foo: int
def __call__(self, *args, **kwargs) -> Any: ...
def f(call: CallableWithNameAndFoo) -> None:
...
I understand this argument, but I don't think it is sound and I think the current approach is sub-optimal. It obscures the distinction between different callable subtypes and encourages sloppy typing. It treats the user as incapable of understanding core concepts of the data model. A distinction is made with other collections ABCs between the more general types (such as Wouldn't it be prudent to follow the data model logic and keep I mean you can see even with the latter relationship that it is inconsistent, if we take the from collections.abc import Callable
from typing import Any
def f(cal: Callable[..., Any], typ: type) -> None:
print(cal.__globals__) # no error
print(typ.__globals__) # error
assert issubclass(type, Callable) # passes I am not trying to be provocative here, but I really believe that mypy is too lenient with callables for no good reason. Thus, what you call "unnecessary pain" for users is what I would call "necessary pain" or "learning the hard way". You are essentially saying: "Well, this is wrong, but... some people are doing it and we don't want to upset them by telling them they are doing it wrong, so... We'll just say this is correct.". I don't find that argument particularly compelling, regardless of the situation. Shouldn't correctness be priority number one? |
The problem that inspired this questions was type-annotating a decorator that takes a function and returns a function with an attribute added (see here) in my hobby project horology. Mypy did not allow to inherit from F = TypeVar('F', bound=Callable)
class CallableWithInterval(Protocol[F]):
interval: float
__call__: F
__name__: str and then annotate the decorator as: @overload
def timed(f: F) -> CallableWithInterval[F]: ... # Bare decorator usage
... If we then treat Callable as having also |
No, the first priority is to help users write better, more robust code. Overly pedantic type checks can hurt rather than help that goal. If you want to pursue the change in this issue further, you could make a PR to mypy changing this behavior and see what the effects are in mypy-primer output (which shows new errors in various open-source projects). |
This is a false dichotomy. "Better" and "more robust" are consequences of correctness first and foremost, among other things of course. Writing less correct code makes it worse and less robust, all else being equal. It is very strange to even come across the term "overly pedantic" in the context of type safety checking. Now I am very curious: Are there other examples, where mypy purposefully holds onto incorrect behavior? But fair enough. I'll look into the code and propose the change as a PR. I don't consider the number of new (correct) errors that change may cause to be of any importance to the argument at hand, but it will be an interesting exercise. |
Is there currently a way to type a basically a "def" - a If not, I think adding a "def" to the typing module might be a good compromise, as then people using attributes such as |
@helpmefindaname yes there is, see Alex's comment above (about callback protocols) |
@hauntsaninja I am not sure, if I don't get it but from my understanding Alex's comment is quite the opposite of my suggestion: |
from __future__ import annotations
from typing import *
class HasName(Protocol):
__named__: str # modified, just for demonstration
def f(fn: Callable[[], None] & HasName):
reveal_type(fn.__named__) # str playground |
The following should be an error, but it currently is not.
IMO the last line should cause a mypy error to the effect of
"Callable" has no attribute "__name__"
. The callable protocol generally does not define__name__
.To further drive this point home, add the following piece:
This means that
Bar
is correctly viewed as being aCallable
subtype (structurally) and__name__
attribute by mypy.Callable
, mypy just assumes it has the__name__
attribute. 🤨Correct me if I am wrong, but these points cannot logically all be true.
The documentation of the
Callable
ABC clearly states that it is a class that defines the__call__
method. Nowhere does it mention the need for aCallable
class to define__name__
(or anything else for that matter).There is also no mention of any attributes/methods other than
__call__
in the glossary entry for callable.Lastly, the relevant section on Callable types of the standard type hierarchy chapter in the data model documentation again broadly defines them as
and further down even explicitly states the following:
I realize that user defined functions do always have the
__name__
attribute (as well as a bunch of others listed in the aforementioned data model docs). But this should make them a subtype ofCallable
.Yet it seems as though mypy treats a
Callable
as a user defined function. My examplef
above can be changed to any of the attributes of user defined functions, such as__kwdefaults__
for example and it would still pass mypy checks. This seems wildly inconsistent with all the documentation I could find, as well as logically inconsistent in itself.I can only assume that there is some historic/pragmatic explanation for this, but I could not find it.
What is the reasoning behind this?
I found a discussion in #5958 that seems to be related, but found no answer to my question.
The text was updated successfully, but these errors were encountered: