Repository navigation
Callable has no attribute __kwdefaults__ #5958
Description
Activity
I think this can be fixed quickly just by a typeshed change, for historical reasons mypy uses
builtins.functioninstead oftypes.FunctionType, see #3171. Fixing that issue is not very hard but tedious, so I would propose in the meantime just copy all the content fromtypes.FunctionTypetobuiltins.function(except for__get__maybe?). @JelleZijlstra what do you think?- addedbugmypy got something wrongmypy got something wrongfalse-positivemypy gave an error on correct codemypy gave an error on correct code
on Nov 27, 2018 Sure. Could we just do
from types import FunctionType as functionin builtins.pyi instead? (Or the other way around.)Could we just do
from types import FunctionType as functionin builtins.pyi insteadThis may work, mypy uses various
builtin_type()functions for this, and there is no guarantee they all will work. Could you please try this? If it works, this will be great.Accessing
__kwdefaults__is not safe, since a callable object can be an instance of an arbitrary class that implements__call__:from typing import Callable def f(fn: Callable[[], None]) -> None: fn() print(fn.__kwdefaults__) # Unsafe class A: def __call__(self) -> None: pass f(A())
If we had intersection types, we could use
Intersection[Callable[[], None], FunctionType]to represent a callable that must be a function object.- added and removedbugmypy got something wrongmypy got something wrongfalse-positivemypy gave an error on correct codemypy gave an error on correct code
on Nov 28, 2018 Perhaps more importantly, type objects and C functions don't have
__kwdefaults__, and they are commonly used as callable objects.The current fallback is already unsafe, but we perhaps shouldn't make it any more unsafe. An alternative approach could be something like this:
- Remove at least
__code__and__annotations__fromfunctionsince they aren't very generally available (removing__name__might cause too much trouble). - If we see an access to some of these missing attributes, suggest casting the callable to
FunctionTypeinstead. Maybe also explain that some callables such as type objects don't have these defined.
- Remove at least
Accessing
__kwdefaults__is not safeEverything except
__module__if already unsafe, but I didn't hear a single complain about this. Avoiding a false negative in an edge case (callable object) at a cost of false positive in a common case (normal function) is not wise.Also it is a false positive, the same error appears for clearly valid code:
def f() -> None: ... f.__kwdefaults__
- addedbugmypy got something wrongmypy got something wrongfalse-positivemypy gave an error on correct codemypy gave an error on correct code
on Nov 28, 2018 Also it is a false positive, the same error appears for clearly valid code:
This is a fair point. However, the use
__kwdefaults__seems very rare, and requiring a cast is not a big burden. In an internal codebase of several million lines of Python I only found a few uses of__kwdefaults__(or the Python 2 equivalent). If this would be a typical result, this seems a very low-priority thing.Everything except module if already unsafe, but I didn't hear a single complain about this.
My argument is that at the moment there is only a weak case for making things even unsafer, since the enabled use case is probably rare.
FunctionTypeas the fallback would be a lie.functionis already a lie, but at least it's not shy about it -- as the type doesn't exist at runtime, it's more clear that something special is going on. We could make this clearer in the stubs by adding some comments.Some attributes like
__name__are defined for multiple kinds of callables and used all over the place, so removing them is probably not a realistic option. But the fact that things are incorrect right now feels like a questionable argument for making things even less correct. We should generally try to make things more correct, unless this would have other drawbacks such as generating many false positives, which doesn't seem to be the case here (but I could be wrong here -- I'm happy to accept evidence that shows otherwise).Also, I'd rather talk about how to fix this in a more principled fashion, even if we won't be doing this in the immediate future. For example, maybe Python function objects should have a different fallback from plain
Callabletypes? Or should we use an intersection type, if/when they are supported?Here are some additional related examples that generate false positives at the moment:
ord.__text_signature__ # Works at runtime, but mypy complains class A: def f(self): pass A().f.__self__ # Works at runtime, but mypy complains
The move to
FunctionTypewouldn't help with these, and I don't immediately see why we should ignore these over supporting__kwdefaults__. Using__self__, at least, seems somewhat common in the wild, perhaps significantly more common than__kwdefaults__.The problem is much wider than the original reported issue, and just fixing the reported issue without considering the wider problem risks moving sideways at best, I think.
To get the ball rolling, here's a random idea about solving the problem more generally:
- Change
functionto a protocol type that contains attributes that most(?) callable objects should have. At least__name__should be here, even if it's unsafe, to avoid breaking existing code. A protocol has the benefit of not being usable inisinstancechecks. It's also more correct, since there is no single concrete class that is a base class of all callables (beyondobject). - For references to user-defined functions infer
FunctionTypeas the callable fallback. This allows access to__kwdefaults__, etc. - For bound methods infer
MethodTypeas the fallback (only when we are certain about this). - When inferring types for variables, normalize callable fallback to the protocol mentioned in (1). This can be important to avoid false positives and general confusion, since there's no way to specify a custom fallback for a callable using the annotation syntax.
- User-defined classes with
__call__would still be compatible with callable types, even though they may not define all the attributes defined by the function protocol. This would be the primary remaining unsafety. - Give an informative note when accessing
__kwdefaults__(and other similar attributes) of callables with the default fallback. - Optionally, figure out a way to infer the correct fallback for C/Python functions defined in stubs. This is probably not super important, though, since generally whether a module is implemented in C or Python should be an implementation detail that programs shouldn't depend on.
This wouldn't directly solve the original issue, but it would have the benefit of more closely modelling what happens at runtime.
Another idea would be to use intersection types, but that would be problematic since types like
FunctionTypedefine a permissible__call__which seems problematic -- an intersection withFunctionTypewould apparently allow arbitrary calls. Using fallback types doesn't have the same problem.- Change
Another example of this appeared in python/typing#598
Hey,
I currently working with decorators and signatures. Each time I assign a signature to __signature__ I need to add a myyp ignore.
Is there any plan to solve this?Reacted by Laurent Gautier, Tommy Smith, Raphael David François, vamshiaruru, Stefan Mejlgaard, Maciej (MJ) Mikulski and Dennis HarropThe original example in this issue is no longer reproducible due to changes made in typeshed.
Here's an example (pycharm is also syntax highlighting it):
Mypy error:
error: "Callable[Any]" has no attribute "__kwdefaults__"