08-容器-API
约 4563 字大约 15 分钟
2026-09-28
Python 中的“容器”不是单一类型。列表、元组、字典、集合、字符串和自定义集合的存储方式不同,但它们会共享一些基础能力:
- 能否被
for遍历; - 能否通过
len()获取数量; - 能否使用
in判断成员; - 是否具有稳定的顺序;
- 使用整数位置、键还是集合关系访问数据。
Python 把这些能力拆成多个小协议,并在 collections.abc 模块中提供对应的抽象基类(Abstract Base Class,ABC)。
本节会讲清:
Iterable、Sized、Container分别代表什么;Collection为什么组合了三项基础能力;Sequence、Mapping和Set的语义差异;- 协议可用、ABC 识别和继承 ABC 为什么是三件不同的事;
- 什么时候只实现特殊方法,什么时候适合继承抽象基类;
- ABC 提供的混入方法如何减少重复实现。
1. 先从三项最小能力开始
现代 Python 中,Collection 建立在三个基础 ABC 之上:
Iterable -> 对象可以迭代
Sized -> 对象有长度
Container -> 对象支持成员判断
Collection = Iterable + Sized + Container它们分别对应三个核心特殊方法:
Iterable -> __iter__
Sized -> __len__
Container -> __contains__先逐个理解这些能力,再看更复杂的容器类型。
2. Iterable:可以迭代
实现 __iter__ 的对象可以通过 iter(obj) 获得迭代器,随后被 for 循环消费。
from collections.abc import Iterable
class NameList:
def __init__(self, names):
self._names = list(names)
def __iter__(self):
return iter(self._names)
names = NameList(["Ada", "Lin", "Grace"])
for name in names:
print(name)
print(isinstance(names, Iterable))输出为:
Ada
Lin
Grace
True__iter__ 必须返回一个迭代器,而不是随意返回另一个容器。这里直接复用内部列表的迭代器:
return iter(self._names)迭代器还要实现 __next__。由于列表迭代器已经正确实现了该协议,NameList 无须自己管理当前位置和结束条件。
可迭代对象与迭代器不是同一个概念
NameList 是可迭代对象:每次调用 iter(names) 都可以创建一个新的迭代器。
first_iterator = iter(names)
second_iterator = iter(names)
print(first_iterator is second_iterator)False迭代器则表示一次正在进行的遍历。它会保存当前位置,并在耗尽后抛出 StopIteration。
iterator = iter(names)
print(next(iterator))
print(next(iterator))
print(next(iterator))Ada
Lin
Grace再次调用 next(iterator) 会抛出 StopIteration。
__getitem__ 的旧式回退与 Iterable 检查
前面的 FrenchDeck 没有实现 __iter__,只靠从 0 开始的 __getitem__ 也能被 for 遍历。然而:
from collections.abc import Iterable
class LegacySequence:
def __getitem__(self, index):
if index >= 3:
raise IndexError
return index
sequence = LegacySequence()
print(list(sequence))
print(isinstance(sequence, Iterable))输出为:
[0, 1, 2]
False这说明:
for和iter()保留了基于__getitem__的序列回退;Iterable的结构化检查关注__iter__,不会模拟所有运行时回退路径。
对于新设计的通用可迭代对象,显式实现 __iter__ 通常更清楚,也能被 Iterable 识别。
3. Sized:具有长度
实现 __len__ 的对象满足 Sized 接口:
from collections.abc import Sized
class Batch:
def __init__(self, records):
self._records = list(records)
def __len__(self):
return len(self._records)
batch = Batch(["r1", "r2", "r3"])
print(len(batch))
print(isinstance(batch, Sized))3
TrueSized 只关心对象是否提供长度能力,不要求对象一定可迭代或支持下标。
例如,一个远程任务计数器可以有长度,却不允许直接遍历任务:
class TaskCount:
def __init__(self, count):
self.count = count
def __len__(self):
return self.count这种设计是否合理取决于领域语义。len(obj) 通常会让使用者联想到“对象包含多少项”,因此不要用它表达毫无关联的数值。
__len__ 必须返回非负整数。这个约定同时影响 len(obj) 和没有 __bool__ 时的真假回退。
4. Container:支持成员判断
实现 __contains__ 的对象满足 Container 接口:
from collections.abc import Container
class AllowedRoles:
def __init__(self, roles):
self._roles = set(roles)
def __contains__(self, role):
return role in self._roles
roles = AllowedRoles({"admin", "editor"})
print("admin" in roles)
print("guest" in roles)
print(isinstance(roles, Container))输出为:
True
False
True这里使用内部集合完成平均 O(1) 的成员查找。
__contains__ 应返回能够表示判断结果的值。in 运算最终会把返回值解释为布尔值,因此最清楚的实现通常直接返回 True 或 False。
能使用 in 不一定意味着实现了 Container
如果对象没有 __contains__,in 还可以回退到迭代:
from collections.abc import Container
class IteratedNames:
def __init__(self, names):
self._names = list(names)
def __iter__(self):
return iter(self._names)
names = IteratedNames(["Ada", "Grace"])
print("Ada" in names)
print(isinstance(names, Container))输出为:
True
False运行时成员判断能够通过迭代完成,但 Container ABC 的结构化检查关注专用的 __contains__。
如果成员查询很常见,并且对象有更高效的索引结构,显式实现 __contains__ 能同时表达语义并改善性能。
5. Collection:组合三项基础能力
Collection 同时继承 Iterable、Sized 和 Container。一个具体类如果继承 Collection,就需要实现:
__iter__
__len__
__contains__下面实现一个只读标签集合:
from collections.abc import Collection
class Tags(Collection):
def __init__(self, values):
self._values = tuple(dict.fromkeys(values))
def __iter__(self):
return iter(self._values)
def __len__(self):
return len(self._values)
def __contains__(self, value):
return value in self._values
tags = Tags(["python", "api", "python"])
print(list(tags))
print(len(tags))
print("api" in tags)
print(isinstance(tags, Collection))输出为:
['python', 'api']
2
True
True构造函数使用 dict.fromkeys(values) 去重并保留首次出现顺序,再转换成元组保存。这个实现选择了“有顺序、无重复、公开只读”的标签语义。
继承 Collection 的好处不只是为了让 isinstance() 返回 True。它还会在类创建和实例化阶段检查抽象方法是否已经实现。如果漏掉 __contains__:
from collections.abc import Collection
class IncompleteCollection(Collection):
def __iter__(self):
return iter(())
def __len__(self):
return 0尝试创建实例时会抛出 TypeError,指出类仍然包含未实现的抽象方法。
Collection 在 Python 3.6 被加入标准库。原书图片中的“Python 3.6 新增”是当时的版本提示;对于现代 Python 3,它早已是稳定接口,不应再理解为实验性新功能。
6. 容器 ABC 的整体关系
原文使用下面的 UML 图概括主要容器 ABC:

阅读这张图时,应把箭头理解为抽象能力的继承和组合,而不是具体对象在内存中的包含关系。
图的核心关系是:
Collection组合Iterable、Sized和Container;Sequence表达按位置排列的序列;Mapping表达键到值的映射;Set表达无重复元素及集合运算;Reversible表达专用反向迭代能力;Sequence具备顺序语义,因此也属于可反向遍历的类型体系。
这张图来自原章节对应版本,是为了帮助建立整体认识,不应当作为现代 Python API 的完整清单。标准库会继续演化,具体抽象方法和混入方法应以当前 collections.abc 文档为准。
7. Sequence:按位置排列的有限序列
Sequence 适合表达类似列表和元组的只读序列语义:
- 元素具有稳定位置;
- 支持
len(sequence); - 支持
sequence[index]; - 通常支持切片、迭代、成员判断、反向遍历、
index()和count()。
继承 Sequence 时,最小核心实现通常是 __len__ 和 __getitem__。ABC 会基于它们提供多项混入方法。
from collections.abc import Sequence
class ReadOnlyLog(Sequence):
def __init__(self, entries):
self._entries = tuple(entries)
def __len__(self):
return len(self._entries)
def __getitem__(self, index):
return self._entries[index]
log = ReadOnlyLog(["start", "connect", "finish", "connect"])
print(len(log))
print(log[1])
print(log[:2])
print(list(reversed(log)))
print("finish" in log)
print(log.index("finish"))
print(log.count("connect"))
print(isinstance(log, Sequence))输出为:
4
connect
('start', 'connect')
['connect', 'finish', 'connect', 'start']
True
2
2
TrueReadOnlyLog 只显式实现了两个抽象方法,但从 Sequence 获得了可用的 __iter__、__contains__、__reversed__、index() 和 count() 等混入实现。
混入方法依赖核心方法的效率
ABC 提供默认实现并不保证对所有数据结构都高效。
如果 __getitem__ 每次访问是 O(n),那么依赖重复索引的混入方法可能变成 O(n²)。标准库文档特别提醒,链表类序列往往应该覆盖 __iter__ 等方法,直接沿节点遍历。
因此,继承 ABC 后仍要检查:
- 默认方法是否符合对象语义;
- 默认算法是否匹配底层数据结构;
- 是否需要覆盖专用方法改善复杂度。
只实现两个方法不等于自动成为 Sequence
下面的类在运行时具有长度和索引能力:
from collections.abc import Sequence
class MinimalSequence:
def __init__(self, values):
self._values = tuple(values)
def __len__(self):
return len(self._values)
def __getitem__(self, index):
return self._values[index]
values = MinimalSequence([1, 2, 3])
print(values[0])
print(list(values))
print(isinstance(values, Sequence))输出为:
1
[1, 2, 3]
False它能像基础序列一样工作,但没有继承或注册为 Sequence,而 Sequence 的 ABC 检查也不会仅凭 __len__ 与 __getitem__ 自动认定所有类都是虚拟子类。
8. Mapping:键到值的映射
Mapping 表达只读映射接口。最小核心方法通常是:
__getitem__(key):按键读取值;__iter__():迭代键;__len__():返回键值对数量。
下面用内部字典实现只读配置:
from collections.abc import Mapping
class Settings(Mapping):
def __init__(self, values):
self._values = dict(values)
def __getitem__(self, key):
return self._values[key]
def __iter__(self):
return iter(self._values)
def __len__(self):
return len(self._values)
settings = Settings({"host": "localhost", "port": 8000})
print(settings["host"])
print(list(settings))
print("port" in settings)
print(settings.get("missing", "default"))
print(list(settings.keys()))
print(list(settings.values()))
print(list(settings.items()))输出为:
localhost
['host', 'port']
True
default
['host', 'port']
['localhost', 8000]
[('host', 'localhost'), ('port', 8000)]Mapping 基于三个核心方法提供 get()、keys()、items()、values()、成员判断和相等性比较等混入行为。
映射的迭代语义是“迭代键”,不是迭代值或键值对。这与内置 dict 保持一致:
for key in settings:
print(key)如果键不存在,__getitem__ 应抛出 KeyError。当前实现把请求委托给内部字典,自然保留该约定。
如果对象需要修改映射,还应考虑 MutableMapping,它在只读核心之上增加 __setitem__ 和 __delitem__ 等要求。不要仅仅因为内部使用可变字典,就自动把外部接口设计成可变映射。
9. Set:集合关系与集合运算
collections.abc.Set 表达集合语义,而不是名为 set 的具体内置类型。集合的核心特征是:
- 每个元素最多出现一次;
- 不通过位置索引访问;
- 支持成员判断;
- 支持子集、交集、并集和差集等集合关系。
继承 Set 时,通常实现 __contains__、__iter__ 和 __len__,ABC 再提供多项集合运算。
from collections.abc import Set
class FeatureSet(Set):
def __init__(self, features):
self._features = frozenset(features)
def __contains__(self, feature):
return feature in self._features
def __iter__(self):
return iter(self._features)
def __len__(self):
return len(self._features)
basic = FeatureSet({"search", "export"})
premium = FeatureSet({"search", "export", "audit"})
print(basic <= premium)
print(basic < premium)
print("audit" in premium)
print(set(basic & premium))
print(set(premium - basic))输出中集合元素的显示顺序可能变化,但含义是:
True
True
True
{'search', 'export'}
{'audit'}Set 的混入方法让 FeatureSet 获得了比较和中缀集合运算。因为集合本身不承诺稳定迭代顺序,测试时不要依赖打印顺序;可以比较集合值,或在展示前排序。
如果集合对象本身需要可哈希,不能仅仅因为内部使用 frozenset 就假设它自动获得正确的 __hash__。哈希必须与相等性保持一致。Set ABC 提供 _hash() 辅助方法,但是否公开哈希能力仍然是类型作者需要明确做出的设计决定。
10. Reversible:专用的反向迭代能力
Reversible 要求实现 __reversed__:
from collections.abc import Reversible
class History(Reversible):
def __init__(self, events):
self._events = list(events)
def __iter__(self):
return iter(self._events)
def __reversed__(self):
return reversed(self._events)
history = History(["created", "reviewed", "deployed"])
print(list(history))
print(list(reversed(history)))['created', 'reviewed', 'deployed']
['deployed', 'reviewed', 'created']注意,Reversible 本身继承 Iterable,所以具体子类还需要满足迭代要求。
一个没有继承 Reversible 的对象,只要同时支持 __len__ 和整数 __getitem__,reversed(obj) 仍可能通过序列回退正常工作。再次说明了“操作能运行”和“ABC 类型检查为真”并不总是等价。
11. 协议可用、ABC 识别与 ABC 继承
这三个层次需要明确区分。
第一层:操作实际可用
只要对象提供解释器需要的能力,操作就可能运行:
class Values:
def __getitem__(self, index):
if index >= 2:
raise IndexError
return index
print(list(Values()))即使没有继承任何 ABC,也能输出 [0, 1]。
第二层:ABC 能识别对象
部分简单 ABC 通过 __subclasshook__ 检查类是否实现相应方法。例如,实现 __iter__ 的类通常会被 Iterable 识别,实现 __len__ 的类通常会被 Sized 识别。
这种检查只确认方法存在,不会验证方法是否完全遵守返回值、异常和语义约定。下面的类可能被 Sized 识别,但实现仍然是错误的:
class BadSize:
def __len__(self):
return "many"isinstance(BadSize(), Sized) 可以为真,调用 len(BadSize()) 仍会抛出 TypeError。
第三层:显式继承 ABC
显式继承可以带来两项额外价值:
- 检查抽象方法是否已经实现;
- 获得 ABC 提供的混入方法。
例如,继承 Sequence 并实现 __len__、__getitem__ 后,可以得到 index() 和 count() 等默认实现。
显式继承也意味着更强的公开承诺:类型作者声明对象遵守完整的 Sequence 或 Mapping 语义,而不只是碰巧拥有几个同名方法。
12. 虚拟子类注册
ABC 还允许把一个已有类注册为虚拟子类:
from collections.abc import Sequence
class ExistingType:
def __init__(self, values):
self._values = tuple(values)
def __len__(self):
return len(self._values)
def __getitem__(self, index):
return self._values[index]
Sequence.register(ExistingType)
value = ExistingType([1, 2, 3])
print(isinstance(value, Sequence))True注册不会修改 ExistingType 的继承树,也不会把 Sequence 的混入方法复制给它。它只是告诉运行时类型检查:这个类承诺符合 Sequence 接口。
因此,注册前必须由开发者自行确认行为完整。ABC 不会检查切片、异常和性能是否正确,也不会补上缺失的 index() 或 count()。
虚拟注册适合兼容无法修改继承关系的已有类,但普通自定义类型通常优先选择显式继承或直接实现所需协议。
13. 什么时候应该继承 ABC
适合继承 ABC 的情况包括:
- 对象确实承诺完整的序列、映射或集合语义;
- 希望在实例化阶段检查必要方法;
- 希望复用经过定义的混入方法;
- 外部 API 会通过
isinstance(obj, SomeABC)判断能力; - 类型的公共身份本来就是一种集合接口。
只实现协议可能更合适的情况包括:
- 只需要少量语言能力,例如支持
len(); - 对象不符合某个大型 ABC 的完整语义;
- 不需要混入方法或运行时类型分类;
- 继承会承诺调用者不应依赖的额外行为。
例如,分页查询结果可能支持迭代,但长度需要昂贵的数据库统计。此时把它包装成完整 Sequence 可能产生错误预期;只实现惰性 __iter__ 更诚实。
14. 组合通常比继承具体容器更可控
本章示例通常在内部保存一个 list、dict 或 set,再只公开需要的协议:
from collections.abc import Sequence
class AuditTrail(Sequence):
def __init__(self, events):
self._events = tuple(events)
def __len__(self):
return len(self._events)
def __getitem__(self, index):
return self._events[index]这样做可以:
- 复用成熟的底层存储;
- 控制哪些修改操作对外可见;
- 保留以后更换内部结构的空间;
- 避免继承
list后意外暴露append()、sort()、切片赋值等全部接口。
如果对象本质上就是一种列表,并且确实要完整保留列表语义,继承具体容器也可能合理。但领域对象通常更适合组合内部容器,再实现明确的 ABC 或协议。
15. 一个完整的只读序列示例
下面用 Sequence 实现一个项目里程碑集合。类只公开读取能力,并通过 ABC 获得通用序列行为。
from collections.abc import Sequence
class Milestones(Sequence):
def __init__(self, names):
self._names = tuple(names)
def __len__(self):
return len(self._names)
def __getitem__(self, index):
return self._names[index]
def __repr__(self):
return f"Milestones({list(self._names)!r})"
if __name__ == "__main__":
milestones = Milestones(["design", "build", "release", "build"])
print(milestones)
print(len(milestones))
print(milestones[0])
print(milestones[1:3])
print(list(milestones))
print(list(reversed(milestones)))
print("release" in milestones)
print(milestones.index("release"))
print(milestones.count("build"))
print(isinstance(milestones, Sequence))输出为:
Milestones(['design', 'build', 'release', 'build'])
4
design
('build', 'release')
['design', 'build', 'release', 'build']
['build', 'release', 'build', 'design']
True
2
2
True这个类明确实现的只有:
__len__:核心序列长度;__getitem__:核心位置访问;__repr__:便于调试的对象表示。
其余遍历、反向遍历、成员判断、index() 和 count() 来自 Sequence 的默认实现。底层元组负责整数索引、负数索引、切片和越界异常。
16. 本节小结
Python 的容器 API 是一组可以组合的能力,而不是单一的父类:
Iterable代表__iter__提供的迭代能力;Sized代表__len__提供的长度能力;Container代表__contains__提供的专用成员判断;Collection组合了迭代、长度和成员判断;Sequence表达按位置组织的有序序列;Mapping表达键到值的映射,并默认迭代键;Set表达唯一元素和集合关系;Reversible表达专用反向迭代能力;- 协议能运行、ABC 能识别、显式继承 ABC 是三个不同层次;
- ABC 的混入方法能减少代码,但必须检查其语义和复杂度;
- 虚拟注册只影响类型识别,不会注入混入实现;
- 领域对象通常适合组合内置容器,并只公开真正需要的接口。
下一节将从容器协议扩展到整个特殊方法体系,按对象表示、转换、迭代、运算、上下文管理和属性访问等用途梳理常见协议。
拓展阅读与查阅
以下资料用于进一步研究或日后查阅。理解本节内容不依赖这些链接。
