以前,此类问题的“解决方案”之一是堆叠@property
, @classmethod
, and @abstractmethod
共同产生“抽象类属性”。
根据CPython 问题 #89519 https://github.com/python/cpython/issues/89519,链接描述符装饰器,例如@classmethod
or @staticmethod
with @property
可能表现得非常糟糕,所以我们决定像这样链接装饰器从 Python 3.11 开始已弃用 https://docs.python.org/3.11/library/functions.html#classmethod,现在使用类似工具会出错mypy
.
如果您确实需要一些行为类似于抽象类属性的东西,那么还有一个替代解决方案,如中所述这条评论 https://github.com/python/cpython/issues/89519#issuecomment-1397547060,特别是如果您需要一个属性来进行一些昂贵/延迟的访问。技巧是补充使用@abstractmethod
带有子类化typing.Protocol
.
from typing import ClassVar, Protocol
class FooBase(Protocol):
foo: ClassVar[str]
class Foo(FooBase):
foo = "hello"
class Bar(FooBase):
pass
Foo()
Bar() # Cannot instantiate abstract class "Bar" with abstract attribute "foo"
请注意,虽然 linter 可以捕获此类错误,但它不会在运行时强制执行,这与创建 的子类不同abc.ABC
如果您尝试使用抽象属性实例化一个类,这会导致运行时错误。
此外,上述方法不支持使用foo = Descriptor()
,类似于用 a 实现属性@property
反而。要涵盖这两种情况,您需要使用以下内容:
from typing import Any, ClassVar, Optional, Protocol, Type, TypeVar, Union
T_co = TypeVar("T_co", covariant=True)
class Attribute(Protocol[T]):
def __get__(self, instance, owner=None) -> T_co:
...
class FooBase(Protocol):
foo: ClassVar[Union[Attribute[str], str]]
class Foo(FooBase):
foo = "hello"
class Foo:
def __get__(self, instance: Any, owner: Optional[Type] = None) -> str:
return "hello"
class Bar(FooBase):
foo = Foo()
Foo()
Bar()
这两个类都通过类型检查,并且实际上按预期在运行时工作,尽管在运行时又没有强制执行任何操作。