29.7. abc — 抽象基类 — Python 文档

来自菜鸟教程
Python/docs/3.6/library/abc
跳转至:导航、​搜索

29.7. 美国广播公司 — 抽象基类

源代码: :source:`Lib/abc.py`



该模块提供了在 Python 中定义 抽象基类 (ABC) 的基础结构,如 PEP 3119 中所述; 请参阅 PEP 了解为什么将其添加到 Python 中。 (另请参阅 PEP 3141numbers 模块,了解基于 ABC 的数字的类型层次结构。)

collections 模块有一些从 ABC 派生的具体类; 当然,这些还可以进一步推导出来。 此外,collections.abc 子模块有一些 ABC,可用于测试类或实例是否提供特定接口,例如,是否可散列或是否是映射。

该模块提供了用于定义 ABC 的元类 ABCMeta 和用于通过继承交替定义 ABC 的辅助类 ABC

class abc.ABC

具有 ABCMeta 作为其元类的辅助类。 使用这个类,可以通过简单地从 ABC 派生来创建抽象基类,避免有时混淆元类的用法,例如:

from abc import ABC

class MyABC(ABC):
    pass

请注意,ABC 的类型仍然是 ABCMeta,因此从 ABC 继承需要有关元类使用的常规预防措施,因为多重继承可能会导致元类冲突。 也可以通过传递 metaclass 关键字并直接使用 ABCMeta 来定义抽象基类,例如:

from abc import ABCMeta

class MyABC(metaclass=ABCMeta):
    pass

3.4 版中的新功能。

class abc.ABCMeta

用于定义抽象基类 (ABC) 的元类。

使用这个元类来创建一个 ABC。 ABC 可以直接子类化,然后充当混合类。 您还可以将不相关的具体类(甚至内置类)和不相关的 ABC 注册为“虚拟子类”——这些及其后代将被内置 issubclass() 函数视为注册 ABC 的子类,但注册 ABC 不会出现在他们的 MRO(方法解析顺序)中,注册 ABC 定义的方法实现也不会被调用(甚至不能通过 super())。 1

使用 ABCMeta 元类创建的类具有以下方法:

register(subclass)

注册 subclass 作为这个 ABC 的“虚拟子类”。 例如:

from abc import ABC

class MyABC(ABC):
    pass

MyABC.register(tuple)

assert issubclass(tuple, MyABC)
assert isinstance((), MyABC)

在 3.3 版更改: 返回注册的子类,以允许用作类装饰器。

3.4 版更改: 要检测对 register() 的调用,可以使用 get_cache_token() 函数。

您还可以在抽象基类中覆盖此方法:

__subclasshook__(subclass)

(必须定义为类方法。)

检查 subclass 是否被认为是这个 ABC 的子类。 这意味着您可以进一步自定义 issubclass 的行为,而无需在要考虑 ABC 子类的每个类上调用 register()。 (此类方法是从 ABC 的 __subclasscheck__() 方法调用的。)

此方法应返回 TrueFalseNotImplemented。 如果它返回 True,则 子类 被视为此 ABC 的子类。 如果它返回 False,则 子类 不被视为此 ABC 的子类,即使它通常是一个子类。 如果它返回 NotImplemented,子类检查将继续使用通常的机制。

要演示这些概念,请查看以下示例 ABC 定义:

class Foo:
    def __getitem__(self, index):
        ...
    def __len__(self):
        ...
    def get_iterator(self):
        return iter(self)

class MyIterable(ABC):

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    def get_iterator(self):
        return self.__iter__()

    @classmethod
    def __subclasshook__(cls, C):
        if cls is MyIterable:
            if any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

MyIterable.register(Foo)

ABC MyIterable 将标准可迭代方法 __iter__() 定义为抽象方法。 这里给出的实现仍然可以从子类中调用。 get_iterator() 方法也是 MyIterable 抽象基类的一部分,但它不必在非抽象派生类中被覆盖。

此处定义的 __subclasshook__() 类方法表示,任何在其 __dict__(或其基类之一的方法中)具有 __iter__() 方法的类,通过 __mro__ 列表访问)也被认为是 MyIterable

最后,最后一行使 Foo 成为 MyIterable 的虚拟子类,即使它没有定义 __iter__() 方法(它使用旧式可迭代协议,定义为 __len__()__getitem__())。 请注意,这不会使 get_iterator 作为 Foo 的方法可用,因此单独提供。

abc 模块还提供了以下装饰器:

@abc.abstractmethod

指示抽象方法的装饰器。

使用这个装饰器需要类的元类是 ABCMeta 或者是从它派生的。 一个具有从 ABCMeta 派生的元类的类不能被实例化,除非它的所有抽象方法和属性都被覆盖。 可以使用任何正常的“超级”调用机制来调用抽象方法。 abstractmethod() 可用于声明属性和描述符的抽象方法。

不支持向类动态添加抽象方法,或在方法或类创建后尝试修改其抽象状态。 abstractmethod() 只影响使用常规继承派生的子类; 使用 ABC 的 register() 方法注册的“虚拟子类”不受影响。

abstractmethod() 与其他方法描述符结合使用时,应作为最内层装饰器使用,如下面的使用示例所示:

class C(ABC):
    @abstractmethod
    def my_abstract_method(self, ...):
        ...
    @classmethod
    @abstractmethod
    def my_abstract_classmethod(cls, ...):
        ...
    @staticmethod
    @abstractmethod
    def my_abstract_staticmethod(...):
        ...

    @property
    @abstractmethod
    def my_abstract_property(self):
        ...
    @my_abstract_property.setter
    @abstractmethod
    def my_abstract_property(self, val):
        ...

    @abstractmethod
    def _get_x(self):
        ...
    @abstractmethod
    def _set_x(self, val):
        ...
    x = property(_get_x, _set_x)

为了正确地与抽象基类机器互操作,描述符必须使用 __isabstractmethod__ 将自己标识为抽象。 通常,如果用于组成描述符的任何方法是抽象的,则此属性应为 True。 例如,Python 的内置 属性 相当于:

class Descriptor:
    ...
    @property
    def __isabstractmethod__(self):
        return any(getattr(f, '__isabstractmethod__', False) for
                   f in (self._fget, self._fset, self._fdel))

笔记

与 Java 抽象方法不同,这些抽象方法可能有一个实现。 这个实现可以通过覆盖它的类的 super() 机制调用。 在使用协作多重继承的框架中,这可以用作超级调用的端点。

abc 模块还支持以下遗留装饰器:

@abc.abstractclassmethod

3.2 版中的新功能。

自 3.3 版起已弃用:现在可以将 classmethodabstractmethod() 一起使用,使该装饰器变得多余。

内置 classmethod() 的子类,表示抽象类方法。 否则它类似于 abstractmethod()

这种特殊情况已被弃用,因为 classmethod() 装饰器现在在应用于抽象方法时被正确标识为抽象:

class C(ABC):
    @classmethod
    @abstractmethod
    def my_abstract_classmethod(cls, ...):
        ...
@abc.abstractstaticmethod

3.2 版中的新功能。

自 3.3 版起已弃用: 现在可以将 staticmethodabstractmethod() 一起使用,使该装饰器变得多余。

内置 staticmethod() 的子类,表示抽象静态方法。 否则它类似于 abstractmethod()

这种特殊情况已被弃用,因为 staticmethod() 装饰器现在在应用于抽象方法时被正确标识为抽象:

class C(ABC):
    @staticmethod
    @abstractmethod
    def my_abstract_staticmethod(...):
        ...
@abc.abstractproperty

自 3.3 版起已弃用:现在可以将 属性property.getter()property.setter()property.deleter()抽象方法一起使用(),使这个装饰器变得多余。

内置 property() 的子类,表示抽象属性。

这种特殊情况已被弃用,因为 property() 装饰器现在在应用于抽象方法时被正确标识为抽象:

class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

上面的例子定义了一个只读属性; 您还可以通过适当地将一个或多个底层方法标记为抽象来定义读写抽象属性:

class C(ABC):
    @property
    def x(self):
        ...

    @x.setter
    @abstractmethod
    def x(self, val):
        ...

如果只有一些组件是抽象的,则只需更新这些组件以在子类中创建具体属性:

class D(C):
    @C.x.setter
    def x(self, val):
        ...

abc 模块还提供以下功能:

abc.get_cache_token()

返回当前抽象基类缓存令牌。

令牌是一个不透明的对象(支持相等性测试),用于标识虚拟子类的抽象基类缓存的当前版本。 在任何 ABC 上每次调用 ABCMeta.register() 时,令牌都会发生变化。

3.4 版中的新功能。

脚注

1
C++ 程序员应该注意 Python 的虚拟基类概念与 C++ 的不同。