abc --- 抽象基底類別

原始碼:Lib/abc.py


如同在 PEP 3119 中所述,該模組提供了在 Python 中定義抽象基底類別 (ABC) 的基礎元件;若想瞭解為什麼需要在 Python 中增加這個模組,請見 PEP 文件。(也請見 PEP 3141 以及 numbers 模組以瞭解基於 ABC 的數字型別階層關係。)

collections 模块中有一些派生自 ABC 的实体类;当然,这些类还可以进一步被派生。 此外,collections.abc 子模块中有一些可被用于测试一个类或实例是否提供了特定接口的 ABC,例如,它是否为 hashable 或者是否为 mapping 等。

該模組提供了一個用來定義 ABC 的元類別 (metaclass) 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 可以像 mix-in 類別一樣直接被子類別繼承。你也可以將不相關的具體類別(甚至是內建類別)和 ABC 註冊為「虛擬子類別 (virtual subclass)」 —— 這些類別以及它們的子類別會被內建函式 issubclass() 識別為已註冊 ABC 的子類別,但是該 ABC 不會出現在其 MRO(Method Resolution Order,方法解析順序)中,由該 ABC 所定義的方法實作也不可呼叫(即使透過 super() 呼叫也不行)。[1]

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

register(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() 函数。

你也可以覆寫 (override) 虛擬基底類別中的這個方法:

__subclasshook__(subclass)

(必須定義為類別方法。)

检查 subclass 是否是该 ABC 的子类。 这意味着你可以进一步定制 issubclass() 的行为而无需在每个你希望作为该 ABC 的子类的类上调用 register()。 (这个类方法是在该 ABC 的 __subclasscheck__() 方法上被调用的。)

该方法应为返回 True , FalseNotImplemented 。如果返回 True ,则 子类 被视为该抽象基类的子类 。如果返回 False ,则 子类 不被视为此抽象基类的子类 ,即使它通常是。 如果返回 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__ (或在其透過 __mro__ 列表訪問的基底類別) 中具有 __iter__() 方法的類別也都會被視為 MyIterable

最后,末尾的行使得 Foo 成为 MyIterable 的一个虚子类,即使它没有定义 __iter__() 方法(它使用了以 __len__()__getitem__() 术语定义的旧式可迭代协议)。 注意这将不会使 get_iterator 成为对 Foo 可用的方法,所以它将被单独地提供。

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

@abc.abstractmethod

用於表示抽象方法的裝飾器。

使用此装饰器要求类的元类是 ABCMeta 或是其派生类。 一个具有派生自 ABCMeta 的元类的类无法被实例化,除非它全部的抽象方法和特征属性均已被重载。 抽象方法可通过任何普通的 'super' 调用机制来调用。 abstractmethod() 可被用于声明特征属性和描述器的抽象方法。

动态地添加抽象方法到一个类,或尝试在方法或类被创建后修改其抽象状态等操作仅在使用 update_abstractmethods() 函数时受到支持。 abstractmethod() 只会影响使用常规继承所派生的子类;通过 ABC 的 register() 方法注册的“虚子类”不会受到影响。

abstractmethod() 与其他方法描述器配合应用时,它应当被应用为最内层的装饰器,如以下用法示例所示:

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

    @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 的内置 property 所做的就等价于:

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

備註

不同於 Java 抽象方法,這些抽象方法可能具有一個實作。這個實作可在覆寫它的類別上透過 super() 機制來呼叫。這在使用協作多重繼承 (cooperative multiple-inheritance) 的框架中,可以被用作 super 呼叫的一個端點 (end-point)。

abc 模块还支持下列旧式装饰器:

@abc.abstractclassmethod

在 3.2 版新加入.

在 3.3 版之後被棄用: 現在可以讓 classmethod 配合 abstractmethod() 使用,使得此裝飾器變得冗餘。

內建 classmethod() 的子類別,表示為一個抽象類別方法。在其他方面它都類似於 abstractmethod()

這個特例已被棄用,因為現在當 classmethod() 裝飾器應用於抽象方法時已會被正確地標識為是抽象的:

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

在 3.2 版新加入.

在 3.3 版之後被棄用: 現在可以讓 staticmethod 配合 abstractmethod() 使用,使得此裝飾器變得冗餘。

內建 staticmethod() 的子類別,表示為一個抽象靜態方法。在其他方面它都類似於 abstractmethod()

這個特例已被棄用,因為現在當 staticmethod() 裝飾器應用於抽象方法時已會被正確地標識為是抽象的:

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

在 3.3 版之後被棄用: 現在可以讓 propertyproperty.getter()property.setter()property.deleter() 配合 abstractmethod() 使用,使得此裝飾器變得冗餘。

內建 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 快取令牌 (cache token)。

此令牌是一個(支援相等性測試的)不透明物件 (opaque object),用於為虛擬子類別標識抽象基底類別快取的當前版本。此令牌會在任何 ABC 上每次呼叫 ABCMeta.register() 時發生更改。

在 3.4 版新加入.

abc.update_abstractmethods(cls)

重新計算一個抽象類別之抽象狀態的函式。如果一個類別的抽象方法在建立後被實作或被修改,則應當呼叫此函式。通常此函式應在一個類別裝飾器內部被呼叫。

回傳 cls,使其能夠用作為類別的裝飾器。

如果 cls 不是 ABCMeta 的實例則不做任何操作。

備註

此函式會假定 cls 的超類別 (superclass) 已經被更新。它不會更新任何子類別。

在 3.10 版新加入.

註解