首页
/ CPython `collections.abc` 容器抽象基类权威指南:接口检测、虚拟子类与混入(Mixin)机制解析

CPython `collections.abc` 容器抽象基类权威指南:接口检测、虚拟子类与混入(Mixin)机制解析

2026-09-06 19:24:44作者:咎竹峻Karen

collections.abc 是 CPython 标准库中用于定义"容器协议接口"的抽象基类(ABC)模块。它允许你在运行时通过 isinstance/issubclass 判断一个对象是否支持某项能力(如可哈希、可迭代、是映射或序列),同时为 SetMappingSequence 等复杂容器提供了基于少量抽象方法的完整混入(mixin)实现。读完本文,你将掌握接口检测的三种机制、全部 ABC 的分类与继承关系,以及如何用十余行代码实现一个行为与内置容器完全一致的容器类。

本文以 collections.abc 官方文档 为骨架,结合其底层实现 Lib/_collections_abc.py 与单元测试 Lib/test/test_collections.py,从协议检测原理、ABC 全景表、分类详解到实战配方逐层展开。适用前提:本文描述的行为以 CPython 当前仓库(3.13/3.14 开发主线)为准。

模块是什么:一段改名史与设计初衷

模块由 collections 中拆分而来(3.3 版本起独立为 collections.abc),实际上在 CPython 源码中它以私有模块 Lib/_collections_abc.py 实现,文件中声明 __name__ = "collections.abc"(见 Lib/_collections_abc.py),注释说明改名的目的是加快解释器启动——MutableMapping 等类型在解释器早期就需要,而 collections 模块会导入大量其他模块(issue #19218)。

该模块根据 PEP 3119 提供容器 ABC,用于回答两类问题:

  • 某类是否提供某个特定接口?例如是否可哈希(hashable)、是否是一个映射(mapping)、是否可迭代(iterable)。
  • 某对象是否属于某个接口的实例?例如 isinstance(x, collections.abc.Sized) 判断能否调用 len(x)

模块头部有一段重要的"维护须知"(Lib/_collections_abc.py):ABC 与普通模块不同,它发布即冻结——一旦发布就不能再新增抽象或混入方法。原因在于:继承 ABC 的子类会自动获得新增的 mixin 方法,但通过 register() 注册的虚拟子类不会,从而破坏 isinstance(someobj, SomeABC) 的契约。因此新增能力必须创建新的 ABC 子类来扩展(如文档注释中举例的 union() 无法加入 Set,只能新建一个继承 Set 的 ABC)。同时因为 ABCMeta 只检查方法存在性而不检查签名,给方法追加可选参数或改名参数名虽不导致 isinstance 出错,但仍属可疑做法。

三种接口检测机制:isinstanceissubclass 到底如何判定

collections.abc 上的 issubclass / isinstance 测试按三种方式工作,理解其区别是使用本模块的前提。

机制一:直接继承(Direct Inheritance)

新写的类可以直接继承某个 ABC。类必须补齐全部抽象方法,其余混入方法(mixin methods)由继承获得、可按需覆写,也可自由添加额外方法:

from collections.abc import Sequence

class C(Sequence):                      # 直接继承
    def __init__(self): ...             # ABC 不需要的额外方法
    def __getitem__(self, index):  ...  # 必须实现的抽象方法
    def __len__(self):  ...             # 必须实现的抽象方法
    def count(self, value): ...         # 可选覆写 mixin 方法

issubclass(C, Sequence)   # True
isinstance(C(), Sequence) # True

机制二:注册虚拟子类(Registration / Virtual Subclass)

对已存在的类(含第三方类与内置类),无需修改其继承关系即可通过 ABC 的 register() 类方法把它登记为"虚拟子类"。被注册的类应当实现完整的 API(所有抽象方法加所有 mixin 方法),从而让 issubclass/isinstance 能可靠地表明该类型支持完整接口;唯一的例外是能从其余 API 自动推导出的方法:

from collections.abc import Sequence

class D:                                 # 不继承 ABC
    def __init__(self): ...              # ABC 不需要的额外方法
    def __getitem__(self, index):  ...   # 抽象方法
    def __len__(self):  ...              # 抽象方法
    def count(self, value): ...          # Mixin 方法
    def index(self, value): ...          # Mixin 方法

Sequence.register(D)                     # 注册代替继承

issubclass(D, Sequence)  # True
isinstance(D(), Sequence) # True

本例中 D 无需实现 __contains____iter____reversed__in 运算符、迭代逻辑与 reversed() 会自动退化为基于 __getitem____len__ 的序列式访问。

虚拟子类在 CPython 内部被广泛使用。仅以 Iterator 为例,它一次性注册了十几种内置迭代器类型(Lib/_collections_abc.py):bytes_iteratordict_keyiterator/dict_valueiterator/dict_itemiteratorlist_iteratorrange_iteratorstr_iteratortuple_iteratorzip_iterator 等。模块启动时还会先采集各内置类型的真实类型对象用于注册(见 Lib/_collections_abc.py),并依据 PEP 3119 将内置容器登记为对应 ABC 的虚拟子类:MutableSet.register(set)Sequence.register(tuple/str/bytes/range/memoryview)MutableMapping.register(dict) 等,从而使 isinstance({}, MutableMapping)isinstance([], MutableSequence) 成立。注意:文档 collections.abc 文档 中汇总这些 ABC 的表格来自文档而非源码中的正式继承声明,但源码注释与 __subclasshook__ 实现可佐证上述行为。

机制三:结构识别(Structural Recognition,通过 __subclasshook__

一些简单接口可直接通过"所需方法是否存在(且未被设为 None)"来识别:

class E:
    def __iter__(self): ...
    def __next__(self): ...

issubclass(E, Iterable)  # True
isinstance(E(), Iterable) # True

这依赖 ABC 覆写的 __subclasshook__。实现上,所有"单点接口" ABC(ContainerHashableIterableSizedCallableReversibleAwaitableBuffer 等)的钩子最终都走同一个私有助手 _check_methods(C, *methods)Lib/_collections_abc.py):遍历类 C 的 MRO,逐个检查方法名是否出现在某个基类的 __dict__ 中;只要某方法被显式置为 None(用于刻意禁用该协议,如 Mapping.__reversed__ = None),即返回 NotImplemented 表示"不算"。

复杂接口不支持这种技术,因为接口不只是方法名的堆叠——它还包含方法间的语义与关联关系。例如仅凭类提供了 __getitem____len____iter__,不足以区分它是 Sequence 还是 Mapping。正因如此,MappingSequenceSet 等复杂 ABC 都没有定义"靠方法存在性判定"的 __subclasshook__(即不实现鸭子识别),只能通过直接继承或注册。唯一的例外见下:Collection 是一个"恰好三个简单检查并集"的多重接口,其钩子直接要求同时具备 __len____iter____contains__Lib/_collections_abc.py)。

abc 模块中的 ABCMeta 提供了全部基础设施(register__instancecheck____subclasshook__ 协议),详见 abc 模块文档

泛型支持与类型标注

自 Python 3.9 起(PEP 585,参见 types-genericalias 相关文档),这些 ABC 支持 [] 下标用法,可直接用作泛型类型标注:collections.abc.Sequence[int]Mapping[str, int]Callable[[int, str], float] 等。实现上,各 ABC 定义了 __class_getitem__ = classmethod(GenericAlias)(见 Lib/_collections_abc.py),其中 Callable 特殊之处在于使用自定义的 _CallableGenericAlias 把参数表与返回值扁平化处理并校验下标形式(Lib/_collections_abc.py),支持 ...ParamSpectyping.Concatenate

Collections Abstract Base Classes:ABC 全景表

下表汇总模块提供的全部 ABC,其中「抽象方法」列是子类必须自行实现的协议方法,「Mixin 方法」列由 ABC 免费提供、可按需覆写:

ABC 继承自 抽象方法 Mixin 方法
Container [1] __contains__
Hashable [1] __hash__
Iterable [1][2] __iter__
Iterator [1] Iterable __next__ __iter__
Reversible [1] Iterable __reversed__
Generator [1] Iterator sendthrow close__iter____next__
Sized [1] __len__
Callable [1] __call__
Collection [1] SizedIterableContainer __contains____iter____len__
Sequence ReversibleCollection __getitem____len__ __contains____iter____reversed__indexcount
MutableSequence Sequence __getitem____setitem____delitem____len__insert 继承的 Sequence 方法及 appendclearreverseextendpopremove__iadd__
ByteString Sequence __getitem____len__ 继承的 Sequence 方法
Set Collection __contains____iter____len__ __le____lt____eq____ne____gt____ge____and____or____sub____rsub____xor____rxor__isdisjoint
MutableSet Set __contains____iter____len__adddiscard 继承的 Set 方法及 clearpopremove__ior____iand____ixor____isub__
Mapping Collection __getitem____iter____len__ __contains__keysitemsvaluesget__eq____ne__
MutableMapping Mapping __getitem____setitem____delitem____iter____len__ 继承的 Mapping 方法及 poppopitemclearupdatesetdefault
MappingView Sized __init____len____repr__
ItemsView MappingViewSet __contains____iter__
KeysView MappingViewSet __contains____iter__
ValuesView MappingViewCollection __contains____iter__
Awaitable [1] __await__
Coroutine [1] Awaitable sendthrow close
AsyncIterable [1] __aiter__
AsyncIterator [1] AsyncIterable __anext__ __aiter__
AsyncGenerator [1] AsyncIterator asendathrow aclose__aiter____anext__
Buffer [1] __buffer__

表注:

  • [1] 这些 ABC 覆写了 abc.ABCMeta.__subclasshook__,通过"所需方法存在且未置为 None"来支持接口测试。这只对简单接口有效;更复杂的接口必须注册或直接继承。
  • [2] 仅当类注册为 Iterable、或具备 __iter__ 方法时,isinstance(obj, Iterable) 才返回 True;它检测不到仅靠 __getitem__ 完成迭代的旧式可迭代对象(如过去的序列协议)。判定对象是否可迭代的唯一可靠方法是调用 iter(obj) 并观察是否抛出 TypeError。文档与源码(Lib/_collections_abc.py)均反复强调这一坑点。

版本演进一览

下表为仓库文档中可确认的关键版本节点:

版本 变更
3.3 模块从 collections 拆分为独立的 collections.abc
3.5 新增 GeneratorCoroutineAwaitableAsyncIterableAsyncIteratorSequence.index 支持 start/stop 参数
3.6 新增 CollectionReversibleAsyncGenerator
3.9 全部 ABC 支持 [] 泛型下标(PEP 585)
3.12 新增 Buffer(PEP 688);ByteString 被弃用(计划 3.17 移除)

各类 ABC 的详细行为解析

单点接口(One-Trick Ponies)

  • Container:为提供 __contains__ 方法的类设计的 ABC。
  • Hashable:为提供 __hash__ 方法的类设计的 ABC。
  • Sized:为提供 __len__ 方法的类设计的 ABC。
  • Callable:为提供 __call__ 方法的类设计的 ABC。用于类型标注时的用法参见 typing 文档中关于可调用对象注解的说明
  • Iterable:为提供 __iter__ 方法的类设计的 ABC。注意其 __subclasshook__ 只查 __iter__,不查 __getitem__Lib/_collections_abc.py)。
  • Collection(3.6 起):"可迭代、可测长度、支持成员测试"的容器 ABC,由 SizedIterableContainer 组合而成。

迭代族:Iterator / Reversible / Generator

  • Iterator:为同时提供 __iter____next__ 的类设计的 ABC。它把 __iter__ 具体实现为 return self(迭代器必须自反),抽象方法仅剩 __next__Iterator.register 一次性注册了全部内置迭代器类型,因此 isinstance(iter([]), Iterator) 恒为 True
  • Reversible(3.6 起):可迭代且额外提供 __reversed__ 的 ABC。它要求 __iter____reversed__ 同时存在。
  • Generator(3.5 起):为按 PEP 342 实现生成器协议(在迭代器基础上扩展 sendthrowclose)的类设计的 ABC。其 mixin 中 __next__ 被实现为 self.send(None)Lib/_collections_abc.py),close() 通过向生成器抛入 GeneratorExit 并吞掉 GeneratorExit/StopIteration 完成清理;真实生成器类型在启动时即被注册。

序列族:Sequence / MutableSequence / 已弃用的 ByteString

Sequence / MutableSequence 分别对应只读与可变序列。源码中混入方法有一个值得注意的实现要点:__iter____reversed__index 等会对底层 __getitem__ 做反复调用(Lib/_collections_abc.py)。因此:

  • __getitem__ 是常数时间访问(如下标索引的数组/列表),mixin 方法呈线性性能;
  • 若底层 __getitem__ 本身是线性的(如链表式存储),mixin 将退化为平方复杂度,此时应自行覆写这些方法。

Sequence.index 的完整签名为 index(value, start=0, stop=None):返回 value 首次出现的下标,找不到时抛 ValueError;支持 start/stop 是可选但推荐的(3.5 起支持),源码中还处理了负索引(start/stop 为负时先加 len(self) 归位)。

MutableSequence 的抽象方法除 __getitem__/__len__ 外还含 __setitem____delitem__insert;mixin 层的 append = insert(len(self), value)popremovereverseextend__iadd__ 都以这五个原语组合实现。对 listbytearray 均做了注册。

ByteString(弃用,3.12 起弃用、计划 3.17 移除)。它本意是同时作为 bytesbytearray 的公共超类型,但该 ABC 从未拥有任何方法,因此"是 ByteString 实例"并不能告诉你对象的任何有用信息;且 memoryview 等常见缓冲类型从未被视为 ByteString 的子类型(无论运行时还是静态类型检查器)。源码中通过元类 _DeprecateByteStringMeta 在使用该类时触发弃用警告,并配合模块级 __getattr__ 在访问属性时才告警、以避免影响正常导入(Lib/_collections_abc.py)。官方建议(源自 PEP 688):

  • 运行时判断对象是否实现缓冲协议:isinstance(obj, collections.abc.Buffer)
  • 类型标注中:用 Buffer,或显式写出支持的类型的并集,例如 bytes | bytearray | memoryview

集合族:Set / MutableSet

Set 只需实现三个抽象方法 __contains____iter____len__,即可免费获得完整的代数集合 API:子集/超集比较(<=<==!=>>=)、交并差对称差(&|-^ 及其反向运算 __rsub__ 等)与 isdisjoint。这些运算的语义与算法参考内置 frozenset 实现,例如比较运算先比长度再逐元素判定,__eq__ 要求长度相等且互为子集(Lib/_collections_abc.py)。

MutableSetSet 上追加 adddiscard 两个抽象方法,并用它们组合出 remove(找不到抛 KeyError)、pop(空集抛 KeyError)、clear(循环 pop,源码注释直言"慢但有效")、以及就地运算 |=&=^=-=(实现上注意了 it is self 时的自运算正确性,见 Lib/_collections_abc.py)。内置 set 被注册为 MutableSetfrozenset 被注册为 Set

映射族:Mapping / MutableMapping 与视图 ABC

Mapping__getitem____iter____len__ 为抽象核心,mixin 提供:

  • get(key, default=None):捕获 KeyError 返回默认值;
  • __contains__:通过尝试 self[key] 是否抛 KeyError 判定成员关系;
  • keys()/items()/values():返回对应的 KeysView/ItemsView/ValuesView 包装(见 Lib/_collections_abc.py);
  • __eq__:对方必须是 Mapping,再比较 dict(self.items()) == dict(other.items())
  • __reversed__ = None:用置 None 的方式刻意禁用反向迭代。

MutableMappingMapping 上追加 __setitem____delitem__ 抽象方法,并基于 __delitem__ 与迭代组合出 pop(支持默认值哨兵 __marker,缺省且找不到时抛 KeyError)、popitemclearupdatesetdefaultupdate 的实现兼容"映射"与"键值对可迭代对象"两种入参,并可追加关键字参数(Lib/_collections_abc.py)。CPython 还在此类上设置了 __abc_tpflags__ 标记 Py_TPFLAGS_MAPPING,用于让解释器据此优化部分操作。

视图 ABCMappingView/ItemsView/KeysView/ValuesView)是所有"动态字典视图"的基类:MappingView 持有一个 _mapping 引用并实现 __len____repr__KeysView/ItemsView 继承 Set 语义(支持集合运算,_from_iterable 覆写为 set(it)),ValuesView 因值可能重复而继承 Collection。内置 dict_keysdict_itemsdict_values 分别注册到对应视图 ABC(Lib/_collections_abc.py)。

异步与协程族

  • Awaitable(3.5 起):可用于 await 表达式的对象。自定义实现须提供 __await__。协程对象与 Coroutine ABC 的实例都是本 ABC 的实例。
  • Coroutine(3.5 起):实现 sendthrowclose 的协程兼容类;自定义实现还须提供 __await__。所有 Coroutine 实例同时也是 Awaitable 实例。
  • AsyncIterable / AsyncIterator(3.5 起):异步迭代协议,分别要求 __aiter__、以及 __aiter__+__anext__AsyncIterator.__aiter__ 的 mixin 返回 self)。
  • AsyncGenerator(3.6 起):按 PEP 492 与 PEP 525 实现异步生成器协议(asendathrowaclose),__anext__ 在 mixin 层实现为 await self.asend(None)

一个容易踩的坑(文档特别标注):在 CPython 中,用 @types.coroutine 装饰的生成器式协程(generator-based coroutine)是 awaitable,但它们没有 __await__ 方法,因此 isinstance(gencoro, Awaitable) 会返回 False。要检测这类对象应使用 inspect.isawaitable()。同样的情况也适用于 Coroutine 检测。

Buffer(3.12 起)

Buffer 是 PEP 688 引入的 ABC,用于标识实现缓冲协议(__buffer__ 方法)的类,抽象签名为 __buffer__(self, flags: int, /) -> memoryview。它替代了已弃用的 ByteString 作为"运行时判断对象支持缓冲协议"的正统手段。注意:Buffer__subclasshook__ 只检查 __buffer__ 存在性,而内置 bytesbytearraymemoryview 等是否被注册/识别为 Buffer 实例取决于解释器内部的协议标记(Py_TPFLAGS_MANAGED_BUFFER 等机制),如需精确判断建议直接阅读 PEP 688 相关实现。

实战配方:Examples and Recipes

ABC 让我们可以直接询问类或实例是否具备特定能力:

import collections.abc

size = None
if isinstance(myvar, collections.abc.Sized):
    size = len(myvar)

几个 ABC 尤其适合作为 mixin 使用,可显著降低实现完整容器 API 的成本。官方文档的经典例子是只实现三个抽象方法、自动获得整套 Set APIListBasedSet——它偏向空间效率而非速度,且不要求元素可哈希:

import collections.abc

class ListBasedSet(collections.abc.Set):
    ''' Alternate set implementation favoring space over speed
        and not requiring the set elements to be hashable. '''
    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__() 方法被自动提供

& 运算之所以可用,是因为 Set.__and__ 被实现为 self._from_iterable(value for value in other if value in self)

关于把 Set/MutableSet 当 mixin 使用的三条注意事项:

  1. 构造约束与 _from_iterable:部分集合运算需要从可迭代对象创建新集合,默认 mixin 假设类构造器签名为 ClassName(iterable)。这一假设被抽离为内部 classmethod _from_iterable,默认实现调用 cls(iterable)(见 Lib/_collections_abc.py)。若你的类构造器签名不同,必须用能"从 iterable 构造实例"的 classmethod(或普通方法)覆写 _from_iterable。例如内置 dict_keys 视图继承 Set 时就把 _from_iterable 覆写为 set(it)
  2. 比较运算的性能覆写:若要覆写比较(通常为速度考虑,语义固定不可改),只需重定义 __le____ge__,其余操作会自动跟随(因为 __lt__ = len 更小 且 __le____eq__ = 长度相等且 __le__ 等)。
  3. 可哈希集合Set mixin 提供了 _hash 方法用于计算集合哈希值(算法刻意与内置 frozenset 保持一致,见 Lib/_collections_abc.py),但它不定义 __hash__——并非所有集合都可哈希或不可变。要获得可哈希集合,应同时继承 SetHashable,再写上 __hash__ = Set._hash

单元测试对上述机制做了完整覆盖(Lib/test/test_collections.py):例如 TestCollectionABCs.test_Set 断言 set/frozenset 均为 Set 实例且 Set 的抽象方法恰为 __contains____iter____len__test_hash_Set 验证两个只实现三个原语、内容相同的 Set 子类实例哈希相等;test_MutableSet 断言 setMutableSetfrozenset 不是。执行 python -m unittest Lib/test/test_collections.py 可运行整套相关回归测试。

使用建议与判定对照

  • 判断对象可迭代:不要依赖 isinstance(obj, Iterable)(它漏掉纯 __getitem__ 对象),直接 iter(obj)
  • 判断"能取长度"isinstance(obj, collections.abc.Sized)
  • 判断"是映射还是序列"isinstanceMapping/Sequence;二者在源码中被标记了不同的 tpflags,且都无法靠方法存在性自动识别,必须继承或注册。
  • 判断"支持缓冲协议":运行时用 isinstance(obj, collections.abc.Buffer),标注用 Buffer 或显式并集 bytes | bytearray | memoryview
  • 给第三方类"贴标签":使用 SomeABC.register(SomeClass),前提是该类完整实现了接口语义。
  • 以最小代价实现完整容器:直接继承 Set/Mapping/MutableSequence 等,仅实现抽象原语,其余由 mixin 自动补齐,再根据性能瓶颈选择性覆写。

综上,collections.abc 是连接"鸭子类型"与"显式接口契约"的桥梁:三类判定机制、一张全景表加一组成熟的 mixin 配方,足以支撑绝大多数运行时协议检测与自定义容器开发的实战需求。

登录后查看全文
热门项目推荐
相关项目推荐