![](/img/trans.png)
[英]How do you have an attribute called “property” and use the @property decorator?
[英]How to have typing support for a static property (using a decorator)
給定一個 static 屬性裝飾器:
class static_property:
def __init__(self, getter):
self.__getter = getter
def __get__(self, obj, objtype):
return self.__getter(objtype)
@staticmethod
def __call__(getter_fn):
return static_property(getter_fn)
這適用於 class,如下所示:
class Foo:
@static_prop
def bar(self) -> int:
return 10
添加呼叫為 static:
>>> print(Foo.bar)
10
我將如何向Foo.bar
static_property
推斷為類型int
而不是Any
?
還是有另一種方法來創建裝飾器以支持類型推斷?
我的首選解決方案:(使用 Python 3.9
- 3.11
測試)
from __future__ import annotations
from collections.abc import Callable
from typing import Generic, TypeVar
R = TypeVar("R")
class static_property(Generic[R]):
def __init__(self, getter: Callable[[], R]) -> None:
self.__getter = getter
def __get__(self, obj: object, objtype: type) -> R:
return self.__getter()
@staticmethod
def __call__(getter_fn: Callable[[], R]) -> static_property[R]:
return static_property(getter_fn)
class Foo:
@static_property
def bar() -> int: # type: ignore[misc]
return 10
詳情見第 3 節)。
當前編寫代碼的方式表明您想要創建一個描述符,您可以用它來裝飾class方法,正如您將 objtype 傳遞給objtype
中的 getter __get__
這一事實所證明的那樣。
另一方面,描述符的名稱 class static_property
和bar
實際上是 static (對實例或類不做任何事情)這一事實表明您實際上想用它裝飾static方法。 這將需要一種非常不同的方法。
但首先,以class作為唯一參數的方法的解決方案。
這可以通過使static_property
描述符 class 根據被修飾方法的返回類型R
通用(在以下示例中為int
和str
)來實現。
from collections.abc import Callable
from typing import Any, Generic, TypeVar
R = TypeVar("R")
class static_property(Generic[R]):
def __init__(self, getter: Callable[[Any], R]) -> None:
self.__getter = getter
def __get__(self, obj: object, objtype: type) -> R:
return self.__getter(objtype)
@staticmethod
def __call__(getter_fn: Callable[[Any], R]) -> "static_property[R]":
return static_property(getter_fn)
class Foo:
@static_property
def bar(cls) -> int:
return 10
@static_property
def baz(cls) -> str:
return "a"
x = Foo().bar
y = Foo.baz
reveal_type(x)
reveal_type(y)
通過mypy --strict
運行它會得到以下 output:
note: Revealed type is "builtins.int"
note: Revealed type is "builtins.str"
Success: no issues found in 1 source file
從技術上講,我們已經遇到了一個問題,因為bar
和baz
方法仍然被類型檢查器視為常規實例方法,這意味着它期望第一個參數是Foo
的實例,而不是 class 本身。 這就是為什么我在描述符方法中使用Any
作為Callable
的參數。
這在這種情況下並不真正相關,因為(正如我上面提到的)這些方法實際上是 static,所以第一個參數是沒有意義的。 更重要的是,由於bar
和baz
方法僅在描述符的__get__
中以未綁定的形式使用,因此注釋在技術上仍然是正確的。
假設您實際上希望static_property
修飾 static 方法,這意味着在這種情況下方法實際上不采用任何 arguments,這將需要不同的方法。
由於內置的staticmethod
class 是Callable
的子類型(由於它顯然支持__call__
協議),我們可以用staticmethod
修飾bar
和baz
方法並將結果對象傳遞給我們自己的static_property.__call__
。
我們可以保留R
,因為typeshed也根據傳遞給它的方法的返回類型將staticmethod
定義為泛型。 (至少對於 Python 3.10+
。)這意味着我們可以保留有關bar
和baz
方法的返回類型的信息,即使我們用staticmethod
修飾它們也是如此。
from collections.abc import Callable
from typing import Generic, TypeVar
R = TypeVar("R")
class static_property(Generic[R]):
def __init__(self, getter: Callable[[], R]) -> None:
self.__getter = getter
def __get__(self, obj: object, objtype: type) -> R:
return self.__getter()
@staticmethod
def __call__(getter_fn: Callable[[], R]) -> "static_property[R]":
return static_property(getter_fn)
class Foo:
@static_property
@staticmethod
def bar() -> int:
return 10
@static_property
@staticmethod
def baz() -> str:
return "a"
x = Foo().bar
y = Foo.baz
reveal_type(x)
reveal_type(y)
mypy --strict
output 仍然與上面的相同,這意味着類型被正確地推斷為int
和str
。
如您所見,getter function 現在可以簡單地注釋為Callable[[], R]
,即一個不采用 arguments 的可調用函數,但返回我們的描述符的類型參數。
staticmethod
方法最后一步是完全不需要使用staticmethod
裝飾器。 不幸的是,這不能通過對其進行子類化並再次根據R
將我們的子類顯式定義為泛型來實現。 這主要是由於staticmethod.__get__
返回一個可調用對象(再次參見 typeshed 以供參考),但我們明確希望它返回R
。
那么阻礙我們的是mypy
(可能還有其他類型檢查器)期望每個沒有用staticmethod
修飾的方法至少接受一個參數。 因此,如果我們將bar
和baz
定義為不占用 arguments ,我們會收到投訴。
到目前為止,除了明確忽略該雜項錯誤並按如下方式進行之外,我沒有找到解決此問題的方法。
from __future__ import annotations
from collections.abc import Callable
from typing import Generic, TypeVar
R = TypeVar("R")
class static_property(Generic[R]):
def __init__(self, getter: Callable[[], R]) -> None:
self.__getter = getter
def __get__(self, obj: object, objtype: type) -> R:
return self.__getter()
@staticmethod
def __call__(getter_fn: Callable[[], R]) -> static_property[R]:
return static_property(getter_fn)
class Foo:
@static_property
def bar() -> int: # type: ignore[misc]
return 10
@static_property
def baz() -> str: # type: ignore[misc]
return "a"
x = Foo().bar
y = Foo.baz
reveal_type(x)
reveal_type(y)
顯然再次正確推斷出類型,但是mypy
會在沒有type: ignore
指令的情況下報錯,說error: Method must have at least one argument
。 描述符仍然可以正常工作,這里沒有實際的類型安全問題。 在這里要明確一點, type: ignore
是因為bar
和baz
的簽名中缺少參數, mypy
通常認為這是不安全的。
這實際上是類型安全的原因是我們再次只處理未綁定的方法bar
和baz
。 裝飾器“消耗”它們,這意味着它們永遠不會被調用綁定到它們的 class 或其實例。 因此,我們的方法不需要任何 arguments。
(順便說一句,我只是在這里使用__future__.annotations
來避免圍繞static_property[R]
的引號。你顯然可以對上面的解決方案 1) 和 2) 做同樣的事情。)
最終,您似乎必須決定哪種解決方案對您最有用。 每個都有自己的缺點。 看起來我們現在不能以 100% 干凈的方式做到這一點,但也許其他人會找到一種方法,或者打字系統可能會改變方式以允許更好的解決方案。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.