Skip to content

"class property" should be Generic[T] #4731

Open

Description

@Conchylicultor

Currently property.__get__ returns Any, which loose information:

class property(object):

This would be more correct for property.__get__ to be generic and return T. Similar to cached_property:

class cached_property(Generic[_T]):

class A:

  @cached_property
  def f(self) -> int:  # f is `cached_property[int]`, f.__get__() -> int
    ...

  @property
  def g(self) -> int:  # f is `property[Any]`, g.__get__() -> Any
    ...

Activity

  1. srittau commented on Nov 3, 2020

    @srittau
    Collaborator

    This makes sense to me. It might even make sense to use two type variables for setting and getting. On the other hand, I don't know what the impact on type checkers is, since I think they usually hard-code properties.

  2. jp-larose commented on May 18, 2021

    @jp-larose

    I was going to open a new issue in response to python/typing#758, but found this one already opened :)

  3. jp-larose commented on May 18, 2021

    @jp-larose

    I seriously doubt this is idea is supported, but I'll ask anyway.... Is it possible to @overload a class like you can for a function/method? The reason I thought of that is it would allow property with one or two TypeVars. I suspect that the vast majority of uses is for the getter and setter of property to use the same type. However, for those cases where they use different types, there needs to be some way to express that.

    @overload
    class property(Generic[_T]):
        fget: Optional[Callable[[Any], _T]]
        fset: Optional[Callable[[Any, _T], None]]
        fdel: Optional[Callable[[Any], None]]
        .... # The remaining signatures
    
    
    @overload
    class property(Generic[_T_contra, _T_co]):
        fget: Optional[Callable[[Any], _T_co]]
        fset: Optional[Callable[[Any, _T_contra], None]]
        fdel: Optional[Callable[[Any], None]]
        .... # The remaining signatures
  4. JelleZijlstra commented on May 18, 2021

    @JelleZijlstra
    Member

    You can overloads its __init__ or __new__ method.

  5. JelleZijlstra commented on May 18, 2021

    @JelleZijlstra
    Member

    Though I guess that wouldn't give you a way to vary the number of type parameters.

  6. davidhalter commented on Mar 9, 2023

    @davidhalter
    Contributor

    How about something like this?

    _C = TypeVar("_C", contravariant=True)
    _R = TypeVar("_R", covariant=True)
    _R2 = TypeVar("_R2", covariant=True)
    _V = TypeVar("_V", contravariant=True)
    _V2 = TypeVar("_V2", contravariant=True)
    
    class property(Generic[_C, _R, _V]):
        fget: Callable[[_C], _R] | None
        fset: Callable[[_C, _V], None] | None
        fdel: Callable[[_C], None] | None
        __isabstractmethod__: bool
        def __init__(
            self,
            fget: Callable[[_C], _R] | None = ...,
            fset: Callable[[_C, _V], None] | None = ...,
            fdel: Callable[[_C], None] | None = ...,
            doc: str | None = ...,
        ) -> None: ...
        def getter(self, __fget: Callable[[_C], _R2]) -> property[_C, _R2, _V]: ...
        def setter(self, __fset: Callable[[_C, _V2], None]) -> property[_C, _R, _V2]: ...
        def deleter(self, __fdel: Callable[[_C], None]) -> property: ...
        def __get__(self, __obj: _C | None, __type: Type[_C] | None = ...) -> Any: ...
        def __set__(self, __obj: _C, __value: _V) -> None: ...
        def __delete__(self, __obj: _C) -> None: ...
    

    Since the undefined type vars default to Never when they are not provided, this should work fine.

    I'm happy to create a pull request if this is something that would potentially be accepted. What do you guys think?

    Edit: After writing this, I realized that @erictraut has written something similar

  7. added
    stubs: improvementImprove/refactor existing annotations, other stubs issues
    and removed on Mar 23, 2024
  8. jonathandung commented on Jul 7, 2026

    @jonathandung
    Contributor

    Perhaps add overloads for __get__?

    @overload
    def __get__(self, instance: _C, owner: type[_C] | None = ..., /) -> _R: ...
    @overload
    def __get__(self, instance: None, owner: type[_C], /) -> Self: ...

    Also I think the title of this issue is pretty misleading ("class properties" were deprecated in Python 3.11). I will await feedback and possibly make a PR, since the ecosystem must have changed a lot in the five years since the last PR, but there is still sure to be much breakage.

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

    stubs: improvementImprove/refactor existing annotations, other stubs issues

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions