Skip to content

Make builtins.property generic #985

Closed
@sobolevn

Description

@sobolevn

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: ...

Source: https://github.com/python/typeshed/blob/12b79f64d7241024447eae8ca5d52779cca94ee7/stdlib/builtins.pyi#L947

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:

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")},

Here: https://github.com/python/cpython/blob/c8749b578324ad4089c8d014d9136bc42b065343/Objects/descrobject.c#L1567-L1568

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: ...

Playground: https://mypy-play.net/?mypy=latest&python=3.10&flags=strict%2Cdisallow-subclassing-any%2Cdisallow-untyped-calls%2Cdisallow-untyped-decorators&gist=e7cec4565d416a0981d4a65e619f1be6

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:

Looking forward to your feedback! 😊

Related issues

Refs:

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: featureDiscussions about new features for Python's type annotations

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions