Repository navigation
Make builtins.property generic #985
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 @propertys are used, but it quite importatnt for type-checkers and type stubs.
Here are some problems that we have in mypy with properties:
- Because
@propertyis so special right now,mypydoes not support decorated properties: Decorated property not supported mypy#1362 - Mypy does not support
@propertywith different@settertype: 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: makepropertygeneric cpython#30238 - Send a PR to
typeshedwith the typing stub - Maybe update PEP585?
Looking forward to your feedback! 😊
Related issues
Refs:
- Add typing.Property #594 (I think that adding
typing.Propertydoes not really make much sense, sincepython>=3.9already supports other builtin generic types). - Feature Request: Add a generic property class #758
Activity
AlexWaygood commentedon Dec 19, 2021
See also:
propertygeneric: make property generic typeshed#5987hauntsaninja commentedon Dec 19, 2021
(In particular, see erictraut's comments on the second link)
sobolevn commentedon Dec 19, 2021
To address some of @erictraut's feedback about
@propertiesin python/typeshed#5987Yes, this is my main motivation when I suggest adding generic properties support.
At least in mypy we can remove quite a lot of special cases and make
propertya first-class citizen.This can easily be addressed:
property[int, NoReturn]is a read-only propertyproperty[NoReturn, int]is a write-only property__delete__can be special cased in type-checkers, because as Eric said, no one actually uses it. And there's no concrete type information required@.setteras well (mutability is not representable in Python's type system)From my work on decorated
propertysupport inmypy, we can see that there are not so many errors in mypy_primer output. See related comment: python/mypy#11719 (comment)ariebovenberg commentedon Dec 20, 2021
Another big opportunity of generic
propertycould be easier annotation of read-only attributes.This would be a big win for type stubs, as well as
Protocols. A quick look at typeshed shows >10% ofdefs there are used simply for properties. A more readable representation would be a big win!See also: #922 (comment)
also: since the get-only case is so common, it'd probably be logical to allow
propertywith one type argument (where the second defaults toNoReturn). This would be consistent with howpropertyon its own creates only a getter by default.gvanrossum commentedon Dec 20, 2021
It's clear there is something valuable here; there seem to be no lack of other issues/PRs where this has been discussed before, so it's clear there's a need. I hope you all solve the puzzle!
IIUC it should be possible to completely remove the special-casing for properties from type checkers, instead deferring completely to the more general descriptor semantics, right? Then we'd still have to debate some finer points about descriptors, but at least the behavior of properties would be fully defined by its definition in typeshed plus descriptor semantics.
JelleZijlstra commentedon Dec 20, 2021
The biggest blocker for that may be the
@property.settermechanic—I don't think that can be mapped to the current type system.(And the same for
@property.deleter, if anyone uses that.)sobolevn commentedon Dec 20, 2021
Yes, with some minor special-case points. Like identifying writable / read-only properties by
@.setterdefinition / its absence. And the same with@.deleter.But, it is specific to
property.setterandproperty.deleterwhich are not a part of a descriptor protocol.While
x = property(get_x, set_x)would be completely typed with this proposal.gvanrossum commentedon Dec 20, 2021
Hm, yeah, we'll need to special-case
.setterand.deleter. Fine, as long it ends up being fully typed then. (I'm guessing one issue is that ordinarily you cannot override a method.)sobolevn commentedon Dec 26, 2021
CC @erictraut and @rchen152 from pyright and pytype 🙂
erictraut commentedon Dec 29, 2021
I played around with this. Here's a variant of the proposed class definition:
Some observations:
propertyclass declaration work with type checkers that were designed to work with the old one and vice versa.Overall, I'm pretty negative on this change.
It would have been nice if
propertywere defined as generic earlier, but making the change now would be very disruptive, and it would be a step backward in some respects if type checkers removed all of their custom logic and error messages specifically for properties.sobolevn commentedon Jan 11, 2022
Thanks everyone for their feedback!
Since no one is really interested in this, I am going to close this for now.
Maybe later we can revisit this decision.
ofek commentedon Dec 17, 2022
@sobolevn Hmm, what gave you the impression that no one is interested in this? Many are it seems, including at my workplace.