You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Declare and use attributes of function objects #2087
The code above fails to typecheck, but there's some real code that uses those attributes in a pretty structured way. For example django tags some metadata about the function that allows formatting the function results when displaying on the admin (examples at https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display). So it would be nice to be able to do something like:
classFunctionForAdmin(Callable[[], Any]):
short_description: strboolean: bool=Falseadmin_order_field: strempty_value_display: strsome_function: FunctionForAdmindefsome_function():
return ...
some_function.short_description="What a nice little function!"
And having it properly typechecked... is this possible now?
Type checking code like that could be tricky. Just def some_function() isn't really compatible with FunctionForAdmin using ordinary type checking rules, since it doesn't define the extra attributes and it also isn't an instance of the class. Rewriting it makes it clear:
...
def _some_function():
return ...
some_function: FunctionForAdmin = _some_function # not compatible
some_function.short_description = "What a nice little function!"
# even now boolean etc. are missing from the object
For this to make sense we could perhaps support some form of multi-step initialization and structural subtyping, but it's unclear how exactly. Also Callable is not valid as a base class. We could perhaps define __call__ instead, but that wouldn't be quite as pretty.
What about using a decorator instead that declares that a function conforms to a protocol (it doesn't define any attributes -- they need to come from somewhere else):
Here apply_protocol would generate an intersection type Intersection[AdminAttributes, <callable corresponding to the signature of the function>] for the function. This would have the benefits of working around the subtyping issue and preserving the exact signature of the function (via some magic).
Reacted by Pi, Ivan Levkivskyi, Pavel Savchenko, Neil Girdhar, Rafael Muñoz, Samuel Carlsson and Václav Brožík
Perhaps, though how would you define the type of a function f(int) -> int that also has an attribute foo: int? You can't inherit from Callable[[int], int].
It is possible at runtime (since practically it is just an alias to collections.abc.Callable), but mypy will complain about this. Maybe we can do exactly the same that we do with Tuple[...] -- subclassing Callable will create its copy but with a fallback to an actual instance?
Even if we allowed subclassing from Callable (like in the OP's initial comment), we'd still need some hack in mypy that allows a plain function to be assignable to a variable whose type is a subclass of Callable. See what Jukka wrote above.
we'd still need some hack in mypy that allows a plain function to be assignable to a variable whose type is a subclass of Callable. See what Jukka wrote above.
Exactly, this is why I propose to re-use @declared_type together with subclassing Callable:
But this doesn't follow from the rules for @declared_type -- it would require special-casing in mypy, because the "natural" type of some_function is not a subtype of AdminAttributes.
But this doesn't follow from the rules for @declared_type -- it would require special-casing in mypy, because the "natural" type of some_function is not a subtype of AdminAttributes.
Maybe I misunderstood @declared_type, but I thought it is always like this -- the type in @declared_type contains a more precise type for the function, so it should be the subtype of the type inferred by mypy, not vice versa, for example:
defmagic_deco(func: Any) ->Any:
# do some magicreturnfunc@declared_type(Callable[[int], int]) # this is more precise than a "natural" type Any@magic_decodeffun(x: int) ->int:
...
Hm, I always thought it was the other way around. But what you say makes sense too.
I find the draft text for the PEP rather vague on this, and most unit tests, like your example above, skirt the issue by using Any, which is both a subtype and a supertype of everything.
If I look at the implementation it appears to be checking that the inferred type (i.e. before applying @declared_type) is a subtype of the declared type (i.e. the argument to @declared_type).
(Raising priority to high since this is a popular feature request).
Also it looks like we didn't discuss another popular use case here: adding attributes from another class in a generic manner. For example ...
Such proxy class could for example also check that all attributes of a dataclass were read. That allows to persists type checks of read attributes and also ensure no attributes were kept not accessed. Would play handy when you try to read schema objects and apply those to models in webserver scenario
I was having the same issue (trying to add an action to Django admin) and Google kept bringing me here. I'm pretty new to typing/mypy, so I don't know if this is perfect, but in case its of use to anyone, I was able to solve my issue with the below workaround:
I can even make an example based on this that uses no type annotations at all, and mypy rejects it even without --check-untyped-defs:
classfoo():
defprint(self):
print("Hello World")
return ("fooish")
classbar():
''' A callable class with a return value'''def__new__(self):
returnfoo()
bar().print()
This works fine in python 3.8 but mypy says:
test3.py:12: error: "bar" has no attribute "print" [attr-defined]
I guess I am surprised to see how much valid python mypy restricts, even for completely untyped code.
I'm new to mypy and may well miss the point, but it surprises me given that mypy is advertised as the reference implementation of typing PEPs, yet seems to be imposing non-reference restrictions on the language. Of course I get the argument above, that it may simply be really hard to do.
Here are the related bugs for __new__: #1020#14426#8330
It happens to be related because one may try to use both __new__ or function attributes to create a callable with an arbitrary return type and with "static" attributes that may or may not need to also be externally accessible, and in both cases find that mypy complains. It's also related because of the general issue discussed there regarding if the purpose of mypy should be to enforce good coding practices, in some cases without (I think) even any good way to ignore the enforcement. Should there even be any discussion of valid use cases in a reference implementation?
This is a long and very old issue. Note that you can basically accomplish what is needed using Protocols:
from typing import *
class FunctionForAdmin:
short_description: str
boolean: bool
admin_order_field: str
empty_value_display: str
def __call__(self) -> Any: ... # or whatever signature you want
# Option 1: Declare type of some_function as below, note you may have to # type: ignore[no-redef] or disable that error code
some_function: FunctionForAdmin
# Option 2: Declare a decorator like in https://github.com/python/mypy/issues/2087#issuecomment-462726600, then use it to decorate some_function
def some_function():
return ...
reveal_type(some_function.short_description)
As others have already noted, in some cases you may be better served by using a normal class with __call__ instead of patching attributes onto functions.
If you have needs that are not met by the above, please open a new issue.
@alessio-b2c2 you need to type ignore in your inner function (or make the input type Any), and everything will work nicely. mypy's not going to understand the process of turning a function from Callable into FunctionWithMetadata
Activity
gvanrossum commentedon Sep 2, 2016
Oh wow, nice use case for PEP 526! I guess the equivalent pre-PEP-526 syntax would be
(In a stub the initializer could be
...instead ofNone.)Then all we have to do is change mypy so that this type-checks.
JukkaL commentedon Sep 2, 2016
Type checking code like that could be tricky. Just
def some_function()isn't really compatible withFunctionForAdminusing ordinary type checking rules, since it doesn't define the extra attributes and it also isn't an instance of the class. Rewriting it makes it clear:For this to make sense we could perhaps support some form of multi-step initialization and structural subtyping, but it's unclear how exactly. Also
Callableis not valid as a base class. We could perhaps define__call__instead, but that wouldn't be quite as pretty.What about using a decorator instead that declares that a function conforms to a protocol (it doesn't define any attributes -- they need to come from somewhere else):
Here
apply_protocolwould generate an intersection typeIntersection[AdminAttributes, <callable corresponding to the signature of the function>]for the function. This would have the benefits of working around the subtyping issue and preserving the exact signature of the function (via some magic).dmoisset commentedon Sep 5, 2016
@JukkaL your notation looks ok for me (I wasn't proposing a particular notation, just the general idea in the best way I could write it).
Ignore some warnings when setting function attributes
dgoldstein0 commentedon Aug 22, 2017
any progress here? what's the latest way to work around this, other than just
# type: ignore(my current method)gvanrossum commentedon Aug 23, 2017
No, this hasn't reached criticality yet...
ilevkivskyi commentedon Aug 23, 2017
Maybe this is a situation where we can (re-)use
@declared_typefrom #3291 to specify a more precise type for the function?gvanrossum commentedon Aug 23, 2017
Perhaps, though how would you define the type of a function f(int) -> int that also has an attribute foo: int? You can't inherit from Callable[[int], int].
ilevkivskyi commentedon Aug 23, 2017
It is possible at runtime (since practically it is just an alias to
collections.abc.Callable), but mypy will complain about this. Maybe we can do exactly the same that we do withTuple[...]-- subclassingCallablewill create its copy but with a fallback to an actual instance?gvanrossum commentedon Aug 23, 2017
Even if we allowed subclassing from Callable (like in the OP's initial comment), we'd still need some hack in mypy that allows a plain function to be assignable to a variable whose type is a subclass of Callable. See what Jukka wrote above.
ilevkivskyi commentedon Aug 23, 2017
Exactly, this is why I propose to re-use
@declared_typetogether with subclassingCallable:This way we will not need an additional decorator
@apply_protocol, otherwise the semantics is the same as Jukka proposes.gvanrossum commentedon Aug 23, 2017
But this doesn't follow from the rules for
@declared_type-- it would require special-casing in mypy, because the "natural" type ofsome_functionis not a subtype ofAdminAttributes.ilevkivskyi commentedon Aug 23, 2017
Maybe I misunderstood
@declared_type, but I thought it is always like this -- the type in@declared_typecontains a more precise type for the function, so it should be the subtype of the type inferred by mypy, not vice versa, for example:gvanrossum commentedon Aug 23, 2017
Hm, I always thought it was the other way around. But what you say makes sense too.
I find the draft text for the PEP rather vague on this, and most unit tests, like your example above, skirt the issue by using
Any, which is both a subtype and a supertype of everything.If I look at the implementation it appears to be checking that the inferred type (i.e. before applying
@declared_type) is a subtype of the declared type (i.e. the argument to@declared_type).But this test seems to suggest that you're right.
Where did I go astray?
58 remaining items
erictraut commentedon Jul 25, 2022
If I understand your description correctly, this should work:
Use `Protocol` instead of `Callable` in rate_limit_wrapper_test.
Use `Protocol` instead of `Callable` in rate_limit_wrapper_test.
Use `Protocol` instead of `Callable` in rate_limit_wrapper_test.
Use `Protocol` instead of `Callable` in rate_limit_wrapper_test.
Murtagy commentedon Oct 25, 2022
@ilevkivskyi:
Such proxy class could for example also check that all attributes of a dataclass were read. That allows to persists type checks of read attributes and also ensure no attributes were kept not accessed. Would play handy when you try to read schema objects and apply those to models in webserver scenario
Support additinal attributes in callback protocols (#14084)
alessio-b2c2 commentedon Feb 15, 2023
The idea suggested in #2087 (comment) unfortunately doesn't work with the latest mypy 1.0.0 (at the time of writing):
DougLeonard commentedon Feb 16, 2023
First, for mypy 1.0.0, to get this to work (including actually calling action()) I had to add a dummy __call__ to AdminAttributes, like this:
However mypy also allows this, which is more concise, maybe more standard, and I think in most cases (maybe all?) achieves the same result:
Here's another way to achieve this that mypy does not allow:
mypy says:
test.py:5: error: Incompatible return type for "new" (returns "str", but must return a subtype of "action") [misc]
Maybe that's a mypy requirement, but it doesn't seem to be a python requirement:
discuss.python.org/t/metaclass-new-return-value/13376/6
I can even make an example based on this that uses no type annotations at all, and mypy rejects it even without --check-untyped-defs:
This works fine in python 3.8 but mypy says:
test3.py:12: error: "bar" has no attribute "print" [attr-defined]
I guess I am surprised to see how much valid python mypy restricts, even for completely untyped code.
I'm new to mypy and may well miss the point, but it surprises me given that mypy is advertised as the reference implementation of typing PEPs, yet seems to be imposing non-reference restrictions on the language. Of course I get the argument above, that it may simply be really hard to do.
DougLeonard commentedon Feb 17, 2023
Here are the related bugs for __new__: #1020 #14426 #8330
It happens to be related because one may try to use both __new__ or function attributes to create a callable with an arbitrary return type and with "static" attributes that may or may not need to also be externally accessible, and in both cases find that mypy complains. It's also related because of the general issue discussed there regarding if the purpose of mypy should be to enforce good coding practices, in some cases without (I think) even any good way to ignore the enforcement. Should there even be any discussion of valid use cases in a reference implementation?
hauntsaninja commentedon Aug 10, 2023
This is a long and very old issue. Note that you can basically accomplish what is needed using Protocols:
As others have already noted, in some cases you may be better served by using a normal class with
__call__instead of patching attributes onto functions.If you want to discuss intersection types, python/typing#213 / https://github.com/CarliJoy/intersection_examples is a good place to do this.
If you have needs that are not met by the above, please open a new issue.
@alessio-b2c2 you need to type ignore in your inner function (or make the input type Any), and everything will work nicely. mypy's not going to understand the process of turning a function from Callable into FunctionWithMetadata
mypy: Improve typing for cmdutils/qute_args
Type-annotate serialization.py (pytorch#40862)