迭代协议(Iteration Protocol)是 Python 在处理 for 语句、推导式以及一系列“逐个取值”语法时所遵循的一项核心规则。它规定了解释器在需要“按顺序取得元素”的语义语境中,应当如何检查对象的类型结构,并据此决定采用哪一条获取元素的执行路径。
理解迭代协议,有助于从解释器的工作机制出发,把握 Python 中“可迭代”这一概念的真实含义,而不是将其误解为对象自带的某种抽象能力或接口标记。
一、什么是迭代协议
1、为什么称为“迭代协议”
“迭代”(iteration)并不是对象主动执行的动作,而是解释器在特定语法语境中,对“如何连续取得对象中的元素”这一问题所作出的规则性回答。
当解释器进入需要逐个获取元素的语境时,它并不会假设对象“会迭代”,而是:遵循既定的协议规则,检查对象的类型是否提供了可用于该语境的方法,并据此决定如何取得元素。
迭代协议关注的不是“对象是否支持循环”,而是在“顺序取值”语境中,解释器应如何建立、推进与终止一次取值过程。
2、迭代协议的方法构成
迭代协议的现代解释路径依赖两个核心方法。
(1)容器级协议方法
__iter__(self)(2)迭代器级协议方法
__next__(self)这些方法本身只是普通函数对象,定义在类中,存放于类对象的命名空间里。它们并不会因为名字特殊而自动生效,其语义是否成立,完全取决于解释器是否在特定语法语境中选择进入相应的协议解释路径分支。
3、迭代过程说明
当你编写一段 for 循环时,例如:
for x in [10, 20, 30]:print(x)
从语义抽象层面看,其行为可以概括为:
iter_obj = iter([10, 20, 30]) # 调用 __iter__()while True:try:item = next(iter_obj) # 调用 __next__()print(item)except StopIteration:break
也就是说:
• iter() 触发对象类型的 __iter__ 方法,返回一个迭代器对象
• next() 触发迭代器对象的 __next__() 方法,取出下一个元素
• 当没有数据时,__next__() 抛出 StopIteration 异常,循环结束
在整个迭代过程中,__iter__ 是入口,__next__ 是推进。
二、协议触发的必要条件
迭代协议并不会在任意上下文中被考虑,它的触发同样受到语法语境与类型结构的共同约束。
1、语法触发
解释器仅在以下“需要顺序取值”的语法语境中,才会触发迭代协议的判定流程。
• for x in obj
• 推导式(列表 / 集合 / 字典)和生成器
• tuple(obj)、list(obj) 等基于迭代展开的构造
• *obj 解包语法(不含映射解包)
在这些语境中,解释器的目标并不是“调用某个函数”,而是建立一条可持续取值的执行路径。
2、类型层判定
与大多数协议一致,迭代协议的判定同样发生在类型级别:解释器检查的不是“对象是否有某个属性”,而是“对象的类型是否提供了符合该语境的协议方法”。
由于协议分派基于类型槽位完成,实例字典中动态添加的同名属性不会改变迭代协议的成立判定。
比如:
class A:passa = A()a.__iter__ = lambda: iter([1, 2, 3])for x in a: # TypeError...
这是因为迭代协议的判定发生在类型层,而非实例字典。
三、迭代协议的两条解释路径
当解释器遇到迭代语法语境时,比如 for x in obj: ,并非只有一条固定路径,而是存在明确的优先级分派规则。
1、现代首选路径:从 __iter__ 到 __next__
(1)解释器首先检查对象类型是否提供了迭代入口(通常表现为实现 __iter__ 方法)。
若没有实现 __iter__ 方法,则触发另一条传统路径(下方会阐述)。
若有,则解释器通过 iter(obj) 调用其迭代入口(通常表现为 type(obj).__iter__),并取得一个独立的迭代器对象(iterator)。
(2)随后,解释器在循环中反复调用 next(iterator),即:
type(iterator).__next__(iterator)以逐个取得元素。直至无元素可取时,抛出 StopIteration。
说明:若 __iter__ 返回的对象未提供符合协议的 __next__ 方法,则在调用 next() 时将抛出 TypeError。
(3)解释器捕获到 StopIteration 之后,结束迭代。
示例:
class Count:def __init__(self, n):self.n = ndef __iter__(self):self.i = 0return selfdef __next__(self):if self.i >= self.n:raise StopIterationvalue = self.iself.i += 1return valuefor x in Count(3):print(x,end=" ")
输出:
0 1 2 说明:
• Count(3) 实例对象在 for 语境中承担“可迭代对象角色”
• for 语句触发迭代协议解释路径
• __iter__ 决定了解释器使用哪一个迭代器对象(此处为 self)
• __next__ 决定了如何一步步取得元素
2、兼容路径:__getitem__ 的序列回退
若对象的类型未实现 __iter__,解释器并不会立刻放弃,而是进入一条兼容路径:
(1)检查对象的类型是否实现了 __getitem__。
(2)解释器会从 0 开始,以连续整数索引尝试访问:
obj[0], obj[1], obj[2], ...直至抛出 IndexError(视为序列结束)。若访问过程中抛出其他异常,则异常向外传播。
示例:
class Seq:def __init__(self):self.data = [10, 20, 30]def __getitem__(self, index):return self.data[index]for x in Seq():print(x,end=" ")
输出:
10 20 30 说明:
Seq 并未实现 __iter__,但其类型提供了 __getitem__,解释器因此采用序列回退规则建立取值路径。
四、生成器对象与迭代协议
从迭代协议角度看,生成器对象满足迭代器对象的全部条件:
• 实现了 __next__
• 实现了 __iter__,并且 __iter__ 返回 self
• 通过抛出 StopIteration 终止迭代
比如:
def gen():yield 1yield 2
验证:
g = gen()iter(g) is g # Truenext(g) # 1next(g) # 2next(g) # 抛出 StopIteration
因此,在迭代协议层面,生成器对象既是可迭代对象,又是迭代器对象。
生成器对象之所以能被 for、next() 等机制驱动,完全是因为它实现了迭代协议。
g = gen()# for 循环只会调用 __iter__ 和 __next__for x in g:print(x) # 1 2
五、抽象基类与迭代协议
Python 的 collections.abc 模块定义了几个与迭代协议相关的抽象基类(ABC)。
• Iterable:拥有 __iter__() 方法的对象
• Iterator:既有 __iter__() 又有 __next__() 的对象
from collections.abc import Iterable, Iteratorlst = [1, 2, 3]it = iter(lst)print(isinstance(lst, Iterable)) # Trueprint(isinstance(lst, Iterator)) # Falseprint(isinstance(it, Iterator)) # True
说明:
• 列表是可迭代对象,不是迭代器对象。
• iter(lst) 返回的迭代器对象才具备 __next__() 方法。
六、典型应用场景
迭代协议在 Python 生态中的典型应用包括:
1、for 循环与推导式
2、容器类型(list / dict / set / tuple)
3、文件对象逐行读取
4、生成器与惰性计算
5、自定义数据流与状态机
这些场景的共同点在于:对象并未“实现循环”,而是在特定语法语境中被解释器按协议解释为元素来源。
📘 小结
迭代协议不是对象模型中的实体,而是一组由解释器遵循的语义分派规则。它规定了解释器在需要顺序取值的语法语境中,如何检查对象的类型结构、如何选择取值路径,以及如何判定迭代的开始与终止。对象是否承担“可迭代”或“迭代器”的语义角色,并非其固有属性,而是解释器在特定语境下执行这些规则的结果。
