Description
Hi!
Here's how property
type is defined right now from a typing perspective:
from typing import Any, Callable
class property(object):
fget: Callable[[Any], Any] | None
fset: Callable[[Any, Any], None] | None
fdel: Callable[[Any], None] | None
def __init__(
self,
fget: Callable[[Any], Any] | None = ...,
fset: Callable[[Any, Any], None] | None = ...,
fdel: Callable[[Any], None] | None = ...,
doc: str | None = ...,
) -> None: ...
def getter(self, __fget: Callable[[Any], Any]) -> property: ...
def setter(self, __fset: Callable[[Any, Any], None]) -> property: ...
def deleter(self, __fdel: Callable[[Any], None]) -> property: ...
def __get__(self, __obj: Any, __type: type | None = ...) -> Any: ...
def __set__(self, __obj: Any, __value: Any) -> None: ...
def __delete__(self, __obj: Any) -> None: ...
Note, that it is not generic. But, this is a type that surely can be modeled as a generic type.
Motivation
Why? Because @property
works with two major scenarios: getting some inner type and setting some extrenal type. This can be represented as property[GetType, SetType]
.
It might not seem very important to end users, because of how @property
s are used, but it quite importatnt for type-checkers and type stubs.
Here are some problems that we have in mypy
with properties:
- Because
@property
is so special right now,mypy
does not support decorated properties: Decorated property not supported mypy#1362 - Mypy does not support
@property
with different@setter
type: What to do about setters of a different type than their property? mypy#3004 and Recognize different setter type for properties. mypy#11643
Yes, we can work around the fact we cannot simply express property[GetType, SetType]
, but I don't think that missing this important detail is good for the typing system in the long run.
Without this change we cannot treat @property
as a regular descriptor, which follows the same rules.
Reference implementation
Implementing generic @property
will require several changes.
CPython
I guess we would need something similar as we did in https://www.python.org/dev/peps/pep-0585/
Something like adding:
{"__class_getitem__", Py_GenericAlias, METH_O\|METH_CLASS, PyDoc_STR("See PEP 585")},
typeshed
I am sure that this code can be improved, but it does its job as a reference / demo:
from typing import Any, Callable, Generic
G = TypeVar('G') # think about variance
S = TypeVar('S')
class property(Generic[G, S]):
fget: Callable[[Any], G] | None
fset: Callable[[Any, S], None] | None
fdel: Callable[[Any], None] | None
def __init__(
self,
fget: Callable[[Any], G] | None = ...,
fset: Callable[[Any, S], None] | None = ...,
fdel: Callable[[Any], None] | None = ...,
doc: str | None = ...,
) -> None: ...
def getter(self, __fget: Callable[[Any], G]) -> property: ...
def setter(self, __fset: Callable[[Any, S], None]) -> property: ...
def deleter(self, __fdel: Callable[[Any], None]) -> property: ...
def __get__(self, __obj: Any, __type: type | None = ...) -> Any: ...
def __set__(self, __obj: Any, __value: Any) -> None: ...
def __delete__(self, __obj: Any) -> None: ...
It almost works as-is, but @property.setter
part requires some extra handling from type-checkers.
Backwards compatibility
As far as I understand CPython's development process, we can add this new feature to 3.11 only.
I think that using from __future__ import annotations
should also work for @property
the same way PEP585 defines.
Next steps
If others agree with me that property[GetType, SetType]
is a good thing to have, I can:
- Open new BPO issue for this feature: https://bugs.python.org/issue46162
- Add runtime support for
property.__class_getitem__
to CPython: bpo-46162: makeproperty
generic cpython#30238 - Send a PR to
typeshed
with the typing stub - Maybe update PEP585?
Looking forward to your feedback! 😊
Related issues
Refs:
- Add typing.Property #594 (I think that adding
typing.Property
does not really make much sense, sincepython>=3.9
already supports other builtin generic types). - Feature Request: Add a generic property class #758