迭代是 Python 最底层的统一协议:可迭代对象实现 iter,迭代器实现 next,for 循环只是这对协议的语法包装。理解协议本身,生成器与惰性工具的行为全部自洽。

可迭代对象与迭代器之分

两个角色必须分清:

  • Iterable:实现 __iter__ 并返回迭代器——「可被遍历的东西」:list、str、dict、set、文件、生成器……
  • Iterator:实现 __next__ 产出下一个值、__iter__ 返回自身——「正在遍历的游标」
nums = [1, 2, 3]
it = iter(nums)    # nums.__iter__() → 拿到游标
next(it)           # 1
next(it)           # 2
next(it)           # 3
next(it)           # StopIteration——耗尽信号

迭代器自身也可迭代(__iter__ 返回 self),但可迭代对象不是迭代器:list 每次 __iter__ 都新建游标,因此可以反复遍历。

for 循环的本质

it = iter(xs)             # 1. 向可迭代对象要游标
while True:
    try:
        x = next(it)      # 2. 拉取下一个值
    except StopIteration:
        break             # 3. 耗尽信号 → 静默退出循环
    body(x)               # 循环体

推论一:for 不依赖下标与序列,任何 Iterable 都能遍历。推论二:只实现 __getitem__ 的旧式对象也能被迭代(解释器从 0 逐个索引直到 IndexError),但协议路径优先走 __iter__;__getitem__ 与协议分发的关系见 特殊方法。

耗尽性:一次性游标

迭代器抛出 StopIteration 后终身耗尽,再 next 只会继续抛:

it = iter([1, 2])
list(it)   # [1, 2]
list(it)   # []——空,不是再来一遍

高发事故:同一迭代器交给两段消费逻辑,第二段默默拿到空集。生成器(生成器)是最常见的迭代器形态,同样一次性——需要多次遍历就重新创建,或先物化成 list。

生成器:迭代器的简便实现

函数体内出现 yield,调用即返回 generator 对象——不执行任何函数体代码,且自动实现完整迭代器协议:

def countdown(n):
    while n > 0:
        yield n       # 每次 next 执行到这里暂停、交出值
        n -= 1        # 下次从暂停处继续
 
list(countdown(3))    # [3, 2, 1]
(x * x for x in nums) # 生成器表达式:一次性管道的字面量写法

手写 __iter__/__next__ 的类适合复杂内部状态;简单产出序列,生成器函数与表达式几乎总是更优表达。

惰性求值

迭代器按需产出、从不预物化整个序列:

  • 内存 O(1):处理超大数据流不因集合本身爆内存
  • 可表达无限序列:itertools.count()、逐行读文件
  • 管道化:多级 map/filter 组合不产生中间 list,中途 break 即短路

代价是单遍、单向、无随机访问。组合子工具箱见 itertools。

内建惰性工具

Python 3 的 zip、enumerate、map、filter 全部返回迭代器而非 list:

z = zip("abc", [1, 2, 3])
z                    # <zip object at 0x...>
list(z)              # [('a', 1), ('b', 2), ('c', 3)]
 
sum(map(len, words))          # 直接喂给消费者
(a for a in xs if a > 0)      # filter 的生成器表达式等价物,更常用

细则:

  • zip 按最短输入截断;zip(*xs, strict=True)(3.10+)长度不一致时报错
  • enumerate(xs, start=1) 带下标遍历,比手动维护计数器干净
  • 这些工具相互组合依旧惰性,消费端一处物化即可

API 设计守则:参数收 Iterable、返回 Iterator——上游传 list、tuple、生成器都兼容,下游要物化自己 list()。协议的扩展形态遍布标准库:asyncio 的 async for 是它的异步翻版,itertools 是它的组合子宝库。