说明
《Python 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gr99123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
Python 标准库 collections.abc 模块定义了一些 抽象基类,它们可用于判断一个具体类是否具有某一特定的接口;例如,这个类是否可哈希,或其是否为映射类。
抽象基类(Abstract Base Class)简称 ABC,是对 duck-typing(鸭子类型)的补充,它提供了一种定义接口的新方式,相比之下其他技巧例如 hasattr()
显得过于笨拙或有微妙错误(例如使用 魔术方法)。ABC 引入了虚拟子类,这种类并非继承自其他类,但却仍能被 isinstance()
和 issubclass()
所认可,详细内容在 abc 模块(注意,不是当前介绍的 collections.abc 模块)。Python 自带许多内置的 ABC 用于实现数据结构(在本 collections.abc 模块中)、数字(在 numbers 模块中)、流(在 io 模块中)、导入查找器和加载器(在 importlib.abc 模块中)。你可以使用 abc 模块(注意,不是当前介绍的 collections.abc 模块)来创建自己的 ABC。
此模块(collections.abc)的主要功能有:
新编写的类可以直接从一个抽象基类继承。类必须提供所需的抽象方法。其余的 mixin 方法来自继承,如果需要可以重写,可根据需要添加其他方法:
from collections import abc
class C(abc.Sequence): # 直接继承 inheritance
def __init__(self): ... # ABC 不要求的额外方法
def __getitem__(self, index): ... # 必须的抽象方法
def __len__(self): ... # 必须的抽象方法
def count(self, value): ... # 可选地重写 mixin 方法
>>> issubclass(C, abc.Sequence)
True
>>> isinstance(C(), abc.Sequence)
True
现有类和内置类可以注册为 ABC 的“虚拟子类”。这些类应该定义完整的 API,包括所有的抽象方法和所有的 mixin 方法。这允许用户依靠 isinstance()
和 issubclass()
测试来确定是否支持完整接口。此规则的例外情况是从 API 的其余部分自动推断出的方法:
class D: # 没有继承
def __init__(self): ... # ABC 不要求的额外方法
def __getitem__(self, index): ... # 抽象方法
def __len__(self): ... # 抽象方法
def count(self, value): ... # Mixin 混合方法
def index(self, value): ... # Mixin 混合方法
abc.Sequence.register(D) # 注册而不是继承
>>> issubclass(D, Sequence)
True
>>> isinstance(D(), Sequence)
True
在这个例子中,class D 不需要定义 __contains__
、__iter__
和 __reversed__
, 因为 in 操作、迭代逻辑以及 reversed()
功能会自动回退到 __getitem__
和 __len__
方法。
某些简单接口可以通过所需方法的存在直接识别(除非这些方法已设置为 None):
class E:
def __iter__(self): ...
def __next__(next): ...
>>> issubclass(E, Iterable)
True
>>> isinstance(E(), Iterable)
True
复杂接口不支持这种一种技术,因为接口不仅仅是方法名的存在,接口指定方法之间的语义和关系,这些语义和关系不能仅从特定方法名称的存在来推断。例如,已知一个类提供了__getitem__
、__len__
和 __iter__
,这时就不足以区分序列和映射。
重载是指的是重载 isinstance() 和 issubclass() 的方法。例如以下操作:
from collections import abc
isinstance([], abc.Sequence)
issubclass(list, abc.Sequence)
issubclass(list, abc.MutableSequence)
isinstance((), abc.Sequence)
not issubclass(tuple, abc.MutableSequence)
isinstance("", abc.Sequence)
issubclass(dict, abc.Mapping)
issubclass(bytearray, abc.MutableSequence)
# True
这里提出的主要机制是允许重载内置函数 isinstance() 和 issubclass() 。重载的工作方式如下:
isinstance(x, C)
首先检查 C.__instancecheck__
是否存在,C.__instancecheck__(x)
,而不是其正常实现。类似地,调用 issubclass(D, C)
首先检查 C.__subclasscheck__
是否存在,如果存在,则调用 C.__subclasscheck__(D)
,而不是其正常实现。
请注意,魔术名称不是 __isinstance__
和 __issubclass__
这是因为参数的反转可能会导致混淆,特别是对于 issubclass() 重载。
3.9 新版功能: 这些抽象类现在支持 [],可参考 GenericAlias 类型 和 PEP 585
这个容器模块提供了以下 ABCs:
抽象基类 | 继承自 | 抽象方法 | Mixin 方法 |
---|---|---|---|
Container 1 | __contains__ |
||
Hashable 1 | __hash__ |
||
Iterable 1 2 | __iter__ |
||
Iterator 1 | Iterable | __next__ |
__iter__ |
Reversible 1 | Iterable | __reversed__ |
|
Generator 1 | Iterator | send , throw |
close __iter__ , __next__ |
Sized 1 | __len__ |
||
Callable 1 | __call__ |
||
Collection 1 | Sized, Iterable, Container |
__contains__ ,__iter__ ,__len__ |
|
Sequence | Reversible, Collection |
__getitem__ ,__len__ |
__contains__ ,__iter__ ,__reversed__ ,index , count |
MutableSequence | Sequence | __getitem__ ,__setitem__ ,__delitem__ ,__len__ ,insert |
继承自 Sequence 的方法,append ,reverse ,extend ,pop ,remove ,__iadd__ |
ByteString | Sequence | __getitem__ ,__len__ |
继承自 Sequence 的方法 |
Set | Collection | __contains__ ,__iter__ ,__len__ |
__le__ ,__lt__ , __eq__ , __ne__ ,__gt__ ,__ge__ ,__and__ ,__or__ , __sub__ ,__xor__ , isdisjoint |
MutableSet | Set | __contains__ ,__iter__ ,__len__ ,add ,discard |
继承自 Set 的方法以及 clear ,pop ,remove ,__ior__ ,__iand__ ,__ixor__ ,__isub__ |
Mapping | Collection | __getitem__ ,__iter__ ,__len__ |
__contains__ , keys ,items ,values ,get , __eq__ , __ne__ |
MutableMapping | Mapping | __getitem__ ,__setitem__ ,__delitem__ ,__iter__ ,__len__ |
继承自 Mapping 的方法以及 pop ,popitem ,clear ,update ,和 setdefault |
MappingView | Sized | __len__ |
|
ItemsView | MappingView, Set |
__contains__ ,__iter__ |
|
KeysView | MappingView, Set |
__contains__ ,__iter__ |
|
ValuesView | MappingView, Collection |
__contains__ ,__iter__ |
|
Awaitable 1 | __await__ |
||
Coroutine 1 | Awaitable | send ,throw |
close |
AsyncIterable 1 | __aiter__ |
||
AsyncIterator 1 | AsyncIterable | __anext__ |
__aiter__ |
AsyncGenerator 1 | AsyncIterator | asend ,athrow |
aclose ,__aiter__ , __anext__ |
脚注:
object.__subclasshook__()
通过验证所需方法是否存在且未设置为无来支持测试接口。这只适用于简单的接口。更复杂的接口需要注册或直接子类化。isinstance(obj, Iterable)
检测注册为 Iterable 或具有 __iter__()
方法, 但它不会检测是否有 __getitem__()
,确定对象是否可iter的唯一可靠方法是调用 iter(obj)。class collections.abc.Container
提供了 __contains__()
方法的抽象基类。
class collections.abc.Hashable
提供了 __hash__()
方法的抽象基类。
class collections.abc.Sized
提供了 __len__()
方法的抽象基类。
class collections.abc.Callable
提供了 __call__()
方法的抽象基类。
class collections.abc.Iterable
提供了 iter() 方法的抽象基类。
使用 isinstance(obj, Iterable) 可以检测一个类是否已经注册到了 Iterable 或者实现了 __iter__()
函数,但是无法检测这个类是否能够使用 __getitem__()
方法进行迭代。检测一个对象是否是 iterable 的唯一可信赖的方法是调用 iter(obj)。
class collections.abc.Collection
3.6 新版功能
集合了 Sized 和 Iterable 类的抽象基类。
class collections.abc.Iterator
提供了 __iter__()
和 __next__()
方法的抽象基类。参见 iterator 的定义。
class collections.abc.Reversible
3.6 新版功能
为可迭代类提供了 __reversed__()
方法的抽象基类。
class collections.abc.Generator
3.5 新版功能
生成器类,实现了 PEP 342 中定义的协议,继承并扩展了迭代器,提供了 send(), throw() 和 close() 方法。参见 generator 的定义。
class collections.abc.Sequence
class collections.abc.MutableSequence
class collections.abc.ByteString
在 3.5 版更改: index() 方法支持 stop 和 start 参数
只读且可变的序列 sequences 的抽象基类。
实现笔记:一些混入(Maxin)方法比如 __iter__()
, __reversed__()
和 index() 会重复调用底层的 __getitem__()
方法。因此,如果实现的 __getitem__()
是常数级访问速度,那么相应的混入方法会有一个线性的表现;然而,如果底层方法是线性实现(例如链表),那么混入方法将会是平方级的表现,这也许就需要被重构了。
class collections.abc.Set
class collections.abc.MutableSet
只读且可变的集合的抽象基类。
class collections.abc.Mapping
class collections.abc.MutableMapping
只读且可变的映射 mappings 的抽象基类。
class collections.abc.MappingView
class collections.abc.ItemsView
class collections.abc.KeysView
class collections.abc.ValuesView
映射及其键和值的视图 views 的抽象基类。
class collections.abc.Awaitable
3.5 新版功能
为可等待对象 awaitable 提供的类,可以被用于 await 表达式中。习惯上必须实现 __await__()
方法。
协程 对象和 Coroutine ABC 的实例都是这个 ABC 的实例。
注解: 在 CPython 里,基于生成器的协程(使用 types.coroutine() 或 asyncio.coroutine() 包装的生成器)都是 可等待对象,即使他们不含有 await() 方法。使用 isinstance(gencoro, Awaitable) 来检测他们会返回 False。要使用 inspect.isawaitable() 来检测他们。
class collections.abc.Coroutine
3.5 新版功能
用于协程兼容类的抽象基类。实现了如下定义在 协程对象: 里的方法: send(),throw() 和 close()。通常的实现里还需要实现 await() 方法。所有的 Coroutine 实例都必须是 Awaitable 实例。参见 coroutine 的定义。
注解: 在 CPython 里,基于生成器的协程(使用 types.coroutine() 或 asyncio.coroutine() 包装的生成器)都是 可等待对象,即使他们不含有 await() 方法。使用 isinstance(gencoro, Coroutine) 来检测他们会返回 False。要使用 inspect.isawaitable() 来检测他们。
class collections.abc.AsyncIterable
3.5 新版功能
提供了 __aiter__
方法的抽象基类。参见 asynchronous iterable 的定义。
class collections.abc.AsyncIterator
3.5 新版功能
提供了 __aiter__
和 __anext__
方法的抽象基类。参见 asynchronous iterator 的定义。
class collections.abc.AsyncGenerator
3.6 新版功能
为异步生成器类提供的抽象基类,这些类实现了定义在 PEP 525 和 PEP 492 里的协议。
在面向对象编程领域,与对象交互的使用模式可分为两个基本类别,即“调用”和“检查”( 'invocation' and 'inspection')。调用意味着通过调用对象的方法与对象进行交互。通常这与多态性相结合,因此调用给定方法可能会根据对象的类型运行不同的代码。检查意味着外部代码(对象方法之外)能够检查该对象的类型或属性,并根据该信息决定如何处理该对象。
这两种使用模式服务于相同的通用目的,即能够以统一的方式支持对不同的、潜在的新对象的处理,但同时允许为每种不同类型的对象定制处理决策。
在经典的 OOP (面向对象)理论中,调用是首选的使用模式,而检查不被提倡,被认为是早期过程编程风格的遗迹。然而,在实践中,这种观点过于教条和僵化,导致了一种设计僵化,这与 Python 等语言的动态特性非常不一致。特别是,通常需要以对象类的创建者没有预料到的方式处理对象。在每个对象中嵌入满足该对象的每个可能用户需求的方法并不总是最佳解决方案。此外,还有许多功能强大的分派原理,它们与经典的OOP要求(行为严格封装在对象中)形成直接对比,例如规则或模式匹配驱动逻辑。
另一方面,经典 OOP 理论家对检查的批评之一是缺乏形式主义和被检查内容的特殊性。在 Python 这样的语言中,对象的几乎任何方面都可以被外部代码反映和直接访问,有许多不同的方法来测试对象是否符合特定的协议。例如,如果询问“此对象是可变序列容器吗?”,可以查找“list”的基类,也可以查找名为 __getitem__
的方法。但是请注意,尽管这些测试看起来很明显,但它们都不正确,因为一个测试会产生假阴性,另一个测试会产生假阳性。
普遍同意的补救措施是标准化测试,并将其分组为正式安排。通过继承机制或其他方式,将一组标准的可测试属性与每个类关联,这是最容易做到的。每个测试都带有一组承诺:它包含关于类的一般行为的承诺,以及关于其他类方法可用性的承诺。
Python 提出了一种特殊的策略来组织这些测试,称为抽象基类,称为 ABC。ABCs 只是添加到对象继承树中的 Python 类,用于向外部检查器发送该对象某些特性的信号。测试是使用 isinstance() 完成的,存在特定的 ABC 表示测试已通过。此外,ABC定义了一组最小的方法来建立类型的特征行为。根据ABC类型区分对象的代码可以相信这些方法将始终存在。这些方法中的每一种都伴随着一个广义的抽象语义定义,该定义在ABC的文档中描述。这些标准语义定义不强制执行,但强烈建议使用。
与 Python 中的所有其他东西一样,这些承诺具有绅士协议的性质,这意味着虽然语言确实执行了 ABC 中做出的一些承诺,但具体类的实现者必须确保其余承诺得到遵守。
ABC 允许我们询问类或实例是否提供特定功能,例如:
size = None
if isinstance(myvar, collections.abc.Sized):
size = len(myvar)
有些抽象基类也可以用作混入类(mixin),这可以更容易地开发支持容器 API 的类。例如,要写一个支持完整 Set API 的类,只需要提供下面这三个方法: __contains__()
, __iter__()
和 __len__()
(如果没有定义会报错),抽象基类会补充上其余的方法,比如 __and__()
和 isdisjoint()
:
class ListBasedSet(collections.abc.Set):
''' 支持空间而不是速度的替代集合实现,并且不要求集合元素是可散列的。'''
def __init__(self, iterable):
self.elements = lst = []
for value in iterable:
if value not in lst:
lst.append(value)
def __iter__(self):
return iter(self.elements)
def __contains__(self, value):
return value in self.elements
def __len__(self):
return len(self.elements)
s1 = ListBasedSet('abcdef')
s2 = ListBasedSet('defghi')
overlap = s1 & s2 # `__and__()` 方法是自动支持的
如果没有实现常用方法(见上表),将抛出 TypeError 异常,下例中继承自 Sequence 时没有实现 __getitem__
和 __len__
:
from collections import abc
class MyType(abc.Sequence):
pass
foo = MyType()
# TypeError: Can't instantiate abstract class
# MyType with abstract methods __getitem__, __len__
自定义容器类型时,可以从 collections.abc 模块的抽象类中继承,这些基类能够确保我们的子类具备相应的接口。对于 Set MutableMapping等更为复杂的容器类型来说,如果不继承抽象类,必须实现非常多的特殊方法,才能让自定义的类型符合 Python 编程习惯。
当把 Set 和 MutableSet 用作混入类时需注意:
_from_iterable()
的内部类方法,该方法会调用 cls(iterable)
来产生一个新集合。 如果 Set 混入类在具有不同构造器签名的类中被使用,你将需要通过类方法或常规方法来重载 _from_iterable()
,以便基于可迭代对象参数来构造新的实例。__le__()
和 __ge__()
函数,然后其他的操作会自动跟进。_hash()
方法为集合计算哈希值,然而, __hash__()
函数却没有被定义,因为并不是所有集合都是可哈希并且不可变的。为了使用混入类为集合添加哈希能力,可以同时继承 Set()
和 Hashable()
类,然后定义 __hash__ = Set._hash
。另外:三方库 OrderedSet https://pypi.org/project/ordered-set/) 是基于 MutableSet 构建的一个示例。
总结如下:
更新时间:2021-11-19 21:32:03 标签:python 基类