Skip to content

Add typing.Property #594

Closed as not planned
Closed as not planned

Description

@luckydonald

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__'

Activity

  1. ilevkivskyi commented on Nov 20, 2018

    @ilevkivskyi
    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.

  2. changed the title [-]@property support[/-] [+]Can't access __annotations__ on a @property[/+] on Jun 20, 2019
  3. earonesty commented on May 8, 2020

    @earonesty

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

    >>> Example.batz.fget.__annotations__
    {'return': <class 'str'>}
    
  4. TangoMan75 commented on May 13, 2020

    @TangoMan75

    @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)
  5. jp-larose commented on Sep 3, 2020

    @jp-larose

    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))
  6. luckydonald commented on Sep 3, 2020

    @luckydonald
    Author

    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?

  7. added
    topic: featureDiscussions about new features for Python's type annotations
    on Nov 4, 2021
  8. changed the title [-]Can't access __annotations__ on a @property[/-] [+]Add typing.Property[/+] on Nov 4, 2021
  9. jace commented on Jun 3, 2024

    @jace

    Here's a real-world example for where a generic property is required (screenshots included for easier reference).

    Response.vary is property[HeaderSet], but without the ability to provide a generic type, static type checkers interpret it as Any. When I use response.vary.add('Accept') in my code, there's no type check for the add method:

    Since in #985 it considered too difficult to make builtins.property generic, a substitute typing.Property will be most excellent.

    https://github.com/pallets/werkzeug/blob/3.0.3/src/werkzeug/sansio/response.py#L40-L61

    Property constructor Usage
  10. srittau commented on Jun 3, 2024

    @srittau
    Collaborator

    I don't think we will add typing.property if builtins.property exists. There has been a trend towards deprecating this kind of duplication, i.e. making built-in types generic. This would be the correct course of action for property as well, even if it proves difficult.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: featureDiscussions about new features for Python's type annotations

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions