Skip to content
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

Add typing.Property #594

Open
luckydonald opened this issue Nov 20, 2018 · 5 comments
Open

Add typing.Property #594

luckydonald opened this issue Nov 20, 2018 · 5 comments
Labels
topic: feature Discussions about new features for Python's type annotations

Comments

@luckydonald
Copy link

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__'
@ilevkivskyi
Copy link
Member

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.

@ilevkivskyi ilevkivskyi changed the title @property support Can't access __annotations__ on a @property Jun 20, 2019
@earonesty
Copy link

FYI: if you wind up here, you can do this for now:

>>> Example.batz.fget.__annotations__
{'return': <class 'str'>}

@TangoMan75
Copy link

@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)

@jp-larose
Copy link

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))

@luckydonald
Copy link
Author

luckydonald commented Sep 3, 2020

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?

@srittau srittau added the topic: feature Discussions about new features for Python's type annotations label Nov 4, 2021
@srittau srittau changed the title Can't access __annotations__ on a @property Add typing.Property Nov 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: feature Discussions about new features for Python's type annotations
Projects
None yet
Development

No branches or pull requests

6 participants