Я хочу создать декоратор, который возвращает property
украшенной функции, то есть:
from typing import TYPE_CHECKING
def make_prop(param):
def wrapper(func) -> 'property(func)':
return property(func)
return wrapper
class A:
@make_prop('foo')
def a(self) -> str:
return "hello"
a = A()
assert a.a == "hello"
if TYPE_CHECKING:
reveal_type(a.a)
Вот что печатает reveal_type
note: Revealed type is "Any"
Хотя приведенный выше код работает правильно и тип должен быть str
как? У меня есть reveal_type
там.
Я имею в виду реализацию, потому что я попробовал ваш код, и он работает. Когда я звоню a.a
, он возвращает hello
. и type(a.a)
возвращает str
.
Я знаю, что хочу, чтобы он был совместим с mypy.
Вы можете ввести возвращаемое значение make_prop
в качестве вызываемого объекта-оболочки, который принимает вызываемый объект, возвращающий переменную типа, совпадающую с тем, что возвращает вызываемый объект-оболочка:
from collections.abc import Callable
from typing import TypeVar, cast
T = TypeVar('T')
def make_prop(param) -> Callable[[Callable[..., T]], T]:
def wrapper(func):
return property(func)
return wrapper
Или также ввести внутреннюю функцию-оболочку:
def make_prop(param) -> Callable[[Callable[..., T]], T]:
def wrapper(func: Callable[..., T]) -> T:
return cast(T, property(func))
return wrapper
Демо: https://mypy-play.net/?mypy=latest&python=3.11&gist=afcdaafe99dbdc075a3be650d8582252
Гм, эта реализация заставит средство проверки статического типа думать, что доступ к свойству из класса является фактическим значением.
@dROOOze И в этом весь смысл свойства, так что доступ к свойству ничем не отличается от доступа к фактическому значению.
Нет, доступ из класса дает объект свойства. Доступ из экземпляра дает значение.
@dROOOze Справедливое замечание, хотя я не могу представить реальный вариант использования, когда можно было бы получить доступ к свойству из класса.
Это хороший хак, я его изначально пробовал, и его не хватило декораторам с обертками.
Я понимаю. Тогда я обновил свой ответ вложенным вызовом.
Вы не можете сделать это с помощью property
из встроенных модулей Python, потому что их тип не является универсальным (см. заглушки типизированных).
Как и в большинстве случаев в теле класса, вам нужно понимать дескрипторный протокол Python, чтобы придумать конструкцию типизации, которая делает это правильно. Для начала реализуйте свою собственную универсальную версию property
:
from __future__ import annotations
import collections.abc as cx
import typing as t
R = t.TypeVar("R")
class property_(property, t.Generic[R]):
fget: cx.Callable[[t.Any], R]
fset: cx.Callable[[t.Any, R], None] | None
fdel: cx.Callable[[t.Any], None] | None
if t.TYPE_CHECKING:
def __new__(
cls,
fget: cx.Callable[[t.Any], R],
fset: cx.Callable[[t.Any, R], None] | None = ...,
fdel: cx.Callable[[t.Any], None] | None = ...,
) -> property_[R]: ...
@t.overload
def __get__(self, obj: None, type_: type | None = ...) -> property_[R]: ...
@t.overload
def __get__(self, obj: object, type_: type | None = ...) -> R: ...
def __get__(self, obj: object, type_: type | None = None) -> property_[R] | R: pass
def __set__(self, obj: t.Any, value: R) -> None: ...
Тогда ваш make_prop
можно упростить до
def make_prop(func: cx.Callable[[t.Any], R]) -> property_[R]:
return property_(func)
Окончательно,
class A:
@make_prop
def a(self) -> str:
return "hello"
a: A = A()
assert a.a == "hello"
if t.TYPE_CHECKING:
reveal_type(a.a) # mypy: Revealed type is "builtins.str"
reveal_type(A.a) # mypy: Revealed type is "property_[builtins.str]"
Обратите внимание, что A.a != "hello"
; это то, что обрабатывает первая перегрузка def __get__(self, obj: None, type_: type | None = ...) -> property_[R]: ...
.
Поскольку вопрос был отредактирован, чтобы вместо этого установить make_prop
в качестве фабрики декораторов, небольшое изменение в make_prop
будет
def make_prop(param: t.Any) -> cx.Callable[[cx.Callable[[t.Any], R]], property_[R]]:
def wrapper(func: cx.Callable[[t.Any], R]) -> property_[R]:
return property_(func)
return wrapper
class A:
@make_prop("foo")
def a(self) -> str:
return "hello"
a: A = A()
assert a.a == "hello"
if t.TYPE_CHECKING:
reveal_type(a.a) # mypy: Revealed type is "builtins.str"
reveal_type(A.a) # mypy: Revealed type is "property_[builtins.str]"
Примечание. Если вы не используете mypy
, это решение может не сработать. В частности, pyright
не очень хорошо справляется с подклассами из property
.
Этого недостаточно для обернутых декораторов, mypy-play.net/…
Обновлено: была опечатка, рабочая ссылка
@ניר это тривиальная модификация для make_prop
быть фабрикой декораторов. Смотрите правки.
Извините за невежество (:
Пожалуйста, исправьте крошечную ошибку no-overload-impl
для __get__
перегрузок: вам в любом случае нужна __get__
реализация без @overload
(детская площадка , связанная проблема mypy). То же самое с многоточием должно быть достаточно.
@SUTerliakov К сожалению, я надеялся, что mypy
уже решил это.
Можете ли вы добавить функцию
reveal_type
?