typeshed
typeshed copied to clipboard
"class property" should be Generic[T]
Currently property.__get__ returns Any, which loose information:
https://github.com/python/typeshed/blob/c58a93b928b07a5123fa8ceebe6087fa262fe238/stdlib/3/builtins.pyi#L877
This would be more correct for property.__get__ to be generic and return T. Similar to cached_property:
https://github.com/python/typeshed/blob/1dd1b701c97e4bff9b8e05fb58ce8d3dd27aa213/stdlib/3/functools.pyi#L113
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
...
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.
I was going to open a new issue in response to https://github.com/python/typing/issues/758, but found this one already opened :)
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
You can overloads its __init__ or __new__ method.
Though I guess that wouldn't give you a way to vary the number of type parameters.
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