说明
《Python 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
Python 标准库 abc 抽象基类模块提供了在 Python 中定义 抽象基类 (ABC) 的组件。collections 模块中有一些派生自 ABC 的具体类;当然这些类还可以进一步被派生。此外,collections.abc 子模块中有一些 ABC 可被用于测试一个类或实例是否提供特定的接口,例如它是否可哈希或它是否为映射等。该模块提供了一个元类 ABCMeta,可以用来定义抽象类,另外还提供一个工具类 ABC,可以用它以继承的方式定义抽象基类。
抽象基类(Abstract Base Class)简称 ABC,是对 duck-typing(鸭子类型)的补充,它提供了一种定义接口的新方式,相比之下其他技巧例如 hasattr()
显得过于笨拙或有微妙错误(例如使用 魔术方法)。ABC 引入了虚拟子类,这种类并非继承自其他类,但却仍能被 isinstance()
和 issubclass()
所认可,详细内容在 abc 模块。Python 自带许多内置的 ABC 用于实现数据结构(在 collections.abc 模块中)、数字(在 numbers 模块中)、流(在 io 模块中)、导入查找器和加载器。你可以使用 abc 模块来创建自己的 ABC。
一个使用 ABCMeta 作为元类的工具类。抽象基类可以通过从 ABC 派生来简单地创建,这就避免了在某些情况下会令人混淆的元类用法,例如:
from abc import ABC
class MyABC(ABC):
pass
注意 ABC 的类型仍然是 ABCMeta,因此继承 ABC 仍然需要关注元类使用中的注意事项,比如可能会导致元类冲突的多重继承。当然你也可以直接使用 ABCMeta 作为元类来定义抽象基类,例如:
from abc import ABCMeta
class MyABC(metaclass=ABCMeta):
pass
3.4 新版功能.
用于定义抽象基类(ABC)的元类。
使用该元类以创建抽象基类。抽象基类可以像 mix-in 类一样直接被子类继承。你也可以将不相关的具体类(包括内建类)和抽象基类注册为“抽象子类” —— 这些类以及它们的子类会被内建函数 issubclass() 识别为对应的抽象基类的子类,但是该抽象基类不会出现在其 MRO(Method Resolution Order,方法解析顺序)中,抽象基类中实现的方法也不可调用(即使通过 super() 调用也不行)。1
使用 ABCMeta 作为元类创建的类含有如下方法:
register(subclass)
将“子类”注册为该抽象基类的“抽象子类”,例如:
from abc import ABC
class MyABC(ABC):
pass
MyABC.register(tuple)
assert issubclass(tuple, MyABC)
assert isinstance((), MyABC)
在 3.3 版更改: 返回注册的子类,使其能够作为类装饰器。
在 3.4 版更改: 你可以使用 get_cache_token() 函数来检测对 register() 的调用。
你也可以在虚基类中重载这个方法。
__subclasshook__(subclass)
(必须定义为类方法。)
检查 subclass 是否是该抽象基类的子类。也就是说对于那些你希望定义为该抽象基类的子类的类,你不用对每个类都调用 register() 方法了,而是可以直接自定义 issubclass 的行为。(这个类方法是在抽象基类的 __subclasscheck__()
方法中调用的。)
该方法必须返回 True, False 或是 NotImplemented。如果返回 True,subclass 就会被认为是这个抽象基类的子类。如果返回 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
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, ...):
...
@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 的内置 property 所做的就等价于:
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 版后已移除: 现在可以让 classmethod 配合 abstractmethod() 使用,使得此装饰器变得冗余。
内置 classmethod() 的子类,指明一个抽象类方法。 在其他方面它都类似于 abstractmethod()。
这个特例已被弃用,因为现在当 classmethod() 装饰器应用于抽象方法时它会被正确地标识为抽象的:
class C(ABC):
@classmethod
@abstractmethod
def my_abstract_classmethod(cls, ...):
...
@abc.abstractstaticmethod
3.2 新版功能.3.3 版后已移除: 现在可以让 staticmethod 配合 abstractmethod() 使用,使得此装饰器变得冗余。
内置 staticmethod() 的子类,指明一个抽象静态方法。 在其他方面它都类似于 abstractmethod()。
这个特例已被弃用,因为现在当 staticmethod() 装饰器应用于抽象方法时它会被正确地标识为抽象的:
class C(ABC):
@staticmethod
@abstractmethod
def my_abstract_staticmethod(...):
...
@abc.abstractproperty
3.3 版后已移除: 现在可以让 property, property.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 上每次调用 ABCMeta.register() 时发生更改。
3.4 新版功能.
重新计算一个抽象类的抽象状态的函数。 如果一个类的抽象方法在类被创建后被实现或被修改则应当调用此函数。 通常,此函数应当在一个类装饰器内部被调用。
返回 cls,使其能够用作类装饰器。
如果 cls 不是 ABCMeta 的子类,则不做任何操作。
注解 此函数会假定 cls 的上级类已经被更新。 它不会更新任何子类。
3.10 新版功能.
在面向对象编程领域,与对象交互的使用模式可分为两个基本类别,即“调用”和“检查”( 'invocation' and 'inspection')。调用意味着通过调用对象的方法与对象进行交互。通常这与多态性相结合,因此调用给定方法可能会根据对象的类型运行不同的代码。检查意味着外部代码(对象方法之外)能够检查该对象的类型或属性,并根据该信息决定如何处理该对象。
这两种使用模式服务于相同的通用目的,即能够以统一的方式支持对不同的、潜在的新对象的处理,但同时允许为每种不同类型的对象定制处理决策。
在经典的 OOP (面向对象)理论中,调用是首选的使用模式,而检查不被提倡,被认为是早期过程编程风格的遗迹。然而,在实践中,这种观点过于教条和僵化,导致了一种设计僵化,这与 Python 等语言的动态特性非常不一致。特别是,通常需要以对象类的创建者没有预料到的方式处理对象。在每个对象中嵌入满足该对象的每个可能用户需求的方法并不总是最佳解决方案。此外,还有许多功能强大的分派原理,它们与经典的OOP要求(行为严格封装在对象中)形成直接对比,例如规则或模式匹配驱动逻辑。
另一方面,经典 OOP 理论家对检查的批评之一是缺乏形式主义和被检查内容的特殊性。在 Python 这样的语言中,对象的几乎任何方面都可以被外部代码反映和直接访问,有许多不同的方法来测试对象是否符合特定的协议。例如,如果询问“此对象是可变序列容器吗?”,可以查找“list”的基类,也可以查找名为 __getitem__
的方法。但是请注意,尽管这些测试看起来很明显,但它们都不正确,因为一个测试会产生假阴性,另一个测试会产生假阳性。
普遍同意的补救措施是标准化测试,并将其分组为正式安排。通过继承机制或其他方式,将一组标准的可测试属性与每个类关联,这是最容易做到的。每个测试都带有一组承诺:它包含关于类的一般行为的承诺,以及关于其他类方法可用性的承诺。
Python 提出了一种特殊的策略来组织这些测试,称为抽象基类,称为 ABC。ABCs 只是添加到对象继承树中的 Python 类,用于向外部检查器发送该对象某些特性的信号。测试是使用 isinstance() 完成的,存在特定的 ABC 表示测试已通过。此外,ABC定义了一组最小的方法来建立类型的特征行为。根据ABC类型区分对象的代码可以相信这些方法将始终存在。这些方法中的每一种都伴随着一个广义的抽象语义定义,该定义在ABC的文档中描述。这些标准语义定义不强制执行,但强烈建议使用。
与 Python 中的所有其他东西一样,这些承诺具有绅士协议的性质,这意味着虽然语言确实执行了 ABC 中做出的一些承诺,但具体类的实现者必须确保其余承诺得到遵守。
更新时间:2021-11-19 14:16:03 标签:python 基类