Skip to content

Feature Request: Add a generic property class #758

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

Closed
jp-larose opened this issue Sep 29, 2020 · 7 comments
Closed

Feature Request: Add a generic property class #758

jp-larose opened this issue Sep 29, 2020 · 7 comments
Labels
resolution: duplicate The idea/problem was already reported topic: feature Discussions about new features for Python's type annotations

Comments

@jp-larose
Copy link

Can you add a generic counterpart to the property decorator class to allow for proper typing of object properties. This would align with many other new classes in the typing library to improve type hinting in our code.

Here's my stab at an implementation:

from __future__ import annotations
from typing import Generic, TypeVar, Callable, Any, cast, get_type_hints, Optional, Type

T = TypeVar('T')


class Property(property, Generic[T]):
    def __init__(self,
                 fget: Optional[Callable[[Any], T]] = None,
                 fset: Optional[Callable[[Any, T], None]] = None,
                 fdel: Optional[Callable[[Any], None]] = None,
                 doc: Optional[str] = None):
        super().__init__(fget=fget, fset=fset, fdel=fdel, doc=doc)

    def __get__(self, obj: Any, obj_type: type = None) -> T:
        return cast(T, super().__get__(obj, obj_type))

    def __set__(self, obj: Any, value: T) -> None:
        super().__set__(obj, value)

    def __delete__(self, obj: Any) -> None:
        super().__delete__(obj)

    def getter(self, fget: Callable[[Any], T]) -> Property[T]:
        return cast(Property[T], super().getter(fget))

    def setter(self, fset: Callable[[Any, T], None]) -> Property[T]:
        return cast(Property[T], super().setter(fset))

    def deleter(self, fdel: Callable[[Any], None]) -> Property[T]:
        return cast(Property[T], super().deleter(fdel))

This would allow us to do the following:

from typing import Property  
IntProperty = Property[int]

class C:
    def __init__(self, int_prop: int, str_prop: str):
        self._int_prop = int_prop
        self._str_prop = str_prop

    @IntProperty
    def int_prop(self) -> int:
        return self._int_prop

    @Property[str]  # Relaxed syntax brought to you by PEP 614
    def str_prop(self) -> str:
        return self._str_prop

    @str_prop.setter
    def str_prop(self, new_str_prop: str) -> None:
        self._str_prop = new_str_prop

I'm not sure if I have all the subtleties of the implementation correct, however my own cursory tests on python 3.9 work.

@gvanrossum
Copy link
Member

I'm not sure what this give beyond the standard property. Is there anything in your example program whose type gets inferred incorrectly in your example if you just use @property? Or is there anything which mypy incorrectly disallows when using that?

@bubblefoil
Copy link

I'm not sure what this give beyond the standard property. Is there anything in your example program whose type gets inferred incorrectly in your example if you just use @property? Or is there anything which mypy incorrectly disallows when using that?

In my case, I have a generic class with a property of that generic type. I can type-hint the @property annotated function and PyCharm would understand it. However, if I type-hint @foo.setter function, PyCharm has no idea what type the setter expects.

from typing import TypeVar, Generic

T = TypeVar('T')


class C(Generic[T]):

    def __init__(self, val: T):
        self._val = val

    @property
    def val(self) -> T:
        return self._val

    @val.setter
    def val(self, val: T):
        self._val = val


c = C('abc')
x: float = c.val  # Warning
c.val = 5  # No problem

@jp-larose
Copy link
Author

Sorry, I never came back to this until now...

The point for me was to ensure/validate that the getter and setter for a given property had a matching type.

@srittau
Copy link
Collaborator

srittau commented May 13, 2021

This honestly sounds like a bug/missing feature in PyCharm, although I think mypy has similar issues. The type of a property is the same as the return type of the getter function. (It's a bit more complicated if it has getter and setter methods accepting different types, but the principle is the same.) I don't think a generic would make any difference in the provided code samples.

@srittau
Copy link
Collaborator

srittau commented May 13, 2021

One thing we could improve is the types of property in typeshed, though. Making it generic there sounds like a good idea to me.

@jp-larose
Copy link
Author

One thing we could improve is the types of property in typeshed, though. Making it generic there sounds like a good idea to me.

I think this might be more appropriate than my initial suggestion.

@srittau srittau added resolution: duplicate The idea/problem was already reported topic: feature Discussions about new features for Python's type annotations and removed enhancement labels Nov 4, 2021
@srittau
Copy link
Collaborator

srittau commented Nov 4, 2021

Closing this as a duplicate of #594 and python/typeshed#4731.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
resolution: duplicate The idea/problem was already reported topic: feature Discussions about new features for Python's type annotations
Projects
None yet
Development

No branches or pull requests

4 participants