强制抽象类属性由具体类实现

2024-03-12

考虑这个抽象类和实现它的类:

from abc import ABC

class FooBase(ABC):
    foo: str
    bar: str
    baz: int

    def __init__(self):
        self.bar = "bar"
        self.baz = "baz"

class Foo(FooBase):
    foo: str = "hello"

这里的想法是Foo实现的类FooBase将需要指定的值foo属性,但其他属性(bar and baz) 不需要被覆盖,因为它们已经由抽象类提供的方法处理。

从 MyPy 类型检查的角度来看,是否可以强制Foo声明属性foo否则会引发类型检查错误?

EDIT:

理由是FooBase是库的一部分,并且应该防止客户端代码在没有指定值的情况下实现它foo. For bar and baz然而,这些完全由图书馆管理,客户并不关心它们。


以前,此类问题的“解决方案”之一是堆叠@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()

这两个类都通过类型检查,并且实际上按预期在运行时工作,尽管在运行时又没有强制执行任何操作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

强制抽象类属性由具体类实现 的相关文章

随机推荐