typing
typing copied to clipboard
Add typing.Property
Currently there is no type for the values @property returns.
a) the returned value of property
has no typing
equivalent
def create_property() -> ???:
some_really_bad_code = True
def setter(value):
some_really_bad_code = value
def getter():
return some_really_bad_code
def deller():
some_really_bad_code = None
return property(getter, setter, deller)
b) There is no consistency in how properties in classes work in comparison to normal variables or functions:
class Example:
foo: str
def bar(self, value: str) -> str:
return value
# end def
@property
def batz(self) -> str:
return 'test2'
# end def
@batz.setter
def batz(self, value: str):
pass
# end def
# end class
# variable
>>> typing.get_type_hints(Example)
{'foo': <class 'str'>}
>>> Example.__annotations__
{'foo': <class 'str'>}
# function
>>> typing.get_type_hints(Example.bar)
{'value': <class 'str'>, 'return': <class 'str'>}
>>> Example.bar.__annotations__
{'value': <class 'str'>, 'return': <class 'str'>}
# property
# not in the class's hints
>>> 'batz' in typing.get_type_hints(Example)
False
>>> typing.get_type_hints(Example.batz)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.4_3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/typing.py", line 1527, in get_type_hints
'or function.'.format(obj))
TypeError: <property object at 0x10e81b868> is not a module, class, method, or function.
>>> Example.batz.__annotations__
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'property' object has no attribute '__annotations__'
Regarding a) this is not really about typing
, technically property is a class, so you can annotate your decorator as just -> property
. Some type checkers may be not happy about this, but then it should be discussed per type checker.
About b) we can add some special casing for property
but this is a more general problem, unless decorator copies the __annotations__
attribute, we can't do much.
FYI: if you wind up here, you can do this for now:
>>> Example.batz.fget.__annotations__
{'return': <class 'str'>}
@earonesty Thank you for putting me on the right path...
I was looking for this trick for so long, I think I should share it here.
Before reaching fget
or fset
attributes from a property, we need to access descriptor statically either from inspect.getattr_static()
or type.__getattribute__()
, otherwise Python would raise AttributeError: 'FooBar' object has no attribute "fget"
since it is trying to access 'fget' on property return value instead of the object itself.
from typing import Optional, Union
from inspect import getattr_static
import unittest
class FooBar:
@property
def foobar(self) -> Optional[str]:
pass
@foobar.setter
def foobar(self, foobar_: str) -> None:
pass
class AnnotationsTest(unittest.TestCase):
def setUp(self):
self.foobar = FooBar()
def test_access_getter_annotations_with_inspect(self):
"""==> object_.fget.__annotations__ should return expected annotations"""
property_object = getattr_static(self.foobar, 'foobar')
self.assertEqual(property_object.fget.__annotations__, {'return': Union[str, None]})
def test_access_setter_annotations_with_inspect(self):
"""==> object_.fset.__annotations__ should return expected annotations"""
property_object = getattr_static(self.foobar, 'foobar')
self.assertEqual(property_object.fset.__annotations__, {'foobar_': str, 'return': None})
def test_access_getter_annotations_with_type_getattribute(self):
"""==> object_.fget.__annotations__ should return expected annotations"""
property_object = type.__getattribute__(type(self.foobar), 'foobar')
self.assertEqual(property_object.fget.__annotations__, {'return': Union[str, None]})
def test_access_setter_annotations_with_type_getattribute(self):
"""==> object_.fset.__annotations__ should return expected annotations"""
property_object = type.__getattribute__(type(self.foobar), 'foobar')
self.assertEqual(property_object.fset.__annotations__, {'foobar_': str, 'return': None})
if __name__ == '__main__':
unittest.main(verbosity=2)
Could we have a generic Property class added to typing
? PEP 614 would allow something like this:
from typing import Property
class C:
def __init__(self, prop: int):
self._prop = prop
@Property[int]
def prop(self):
return self._prop
Here's the gist of the class I'm proposing (details will likely need to be refined):
T_co = TypeVar('T_co', covariant=True)
class Property(property, Generic[T_co]):
def fget(self) -> T_co:
return cast(T_co, super().fget())
def fset(self, value: T_co) -> None:
super().fset(value)
def fdel(self) -> None:
super().fdel()
def getter(self, fget: Callable[[Any], T_co]) -> 'Property[T_co]':
return cast(Property[T_co], super().getter(fget))
def setter(self, fset: Callable[[Any, T_co], None]) -> 'Property[T_co]':
return cast(Property[T_co], super().setter(fset))
def deleter(self, fdel: Callable[[Any], None]) -> 'Property[T_co]':
return cast(Property[T_co], super().deleter(fdel))
I mean for me it would make sense the most to actually annotate the return type for the function the property calls, and maybe the property could automatically adapt to that?
@property
def foo(...) -> int:
to
Union[property, int]
or that a new type idea
Property[int]
Like as long as you annotate the function under the @property
that could automatically be used?