目录
1.引言:为什么它们重要?
在 Python 中,几乎所有“能放在 for…in… 里循环”的东西背后都遵循同一套协议:Iterable → Iterator → StopIteration
。掌握这套体系,不仅能写出更 Pythonic 的代码,还能带来三大收益:
• 内存友好:按需生成值,避免一次性加载大数据;
• 代码解耦:生产者与消费者通过迭代协议解耦;
• 并发友好:生成器让“异步回调地狱”变成“顺序协程”。
2.可迭代(Iterable):协议与检测
2.1 什么是可迭代
官方定义:实现了 __iter__()
方法并返回一个迭代器对象,或者实现了序列协议(__getitem__
从 0 开始且无越界抛 IndexError
)。
2.2 检测手段
from collections.abc import Iterable, Iterator
isinstance([1, 2, 3], Iterable) # True
isinstance('abc', Iterable) # True
isinstance(123, Iterable) # False
2.3 常见误区
• 可迭代 ≠ 迭代器。列表是可迭代,但不是迭代器。
• 文件对象 open() 返回的 file 既是可迭代,也是迭代器;但 list(file)
会把文件指针移到末尾,再次迭代为空。
2.4 自定义可迭代类
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1
要点:
• __iter__
可以返回一个新的迭代器,也可以直接写成生成器函数(如上)。
• 推荐返回新的迭代器实例,而不是 self
,否则多次迭代会共享状态。
3.迭代器(Iterator):状态、惰性、一次性
3.1 迭代器协议
必须同时实现:
• __iter__()
—— 返回自身;
• __next__()
—— 返回下一个值,耗尽时抛 StopIteration
。
3.2 手动迭代示范
it = iter([10, 20, 30])
next(it) # 10
next(it) # 20
next(it) # 30
next(it) # StopIteration
3.3 状态机视角
迭代器是一个有状态的游标,只能前进不能后退,且遍历完即“报废”。
3.4 典型实现
class Squares:
def __init__(self, n):
self.i, self.n = 0, n
def __iter__(self):
return self
def __next__(self):
if self.i >= self.n:
raise StopIteration
self.i += 1
return (self.i - 1) ** 2
4.生成器(Generator):用暂停代替回调
4.1 生成器函数
使用 yield
关键字即可把任何普通函数变成生成器工厂:
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
调用 fib()
并不执行函数体,而是返回一个 generator 对象。
4.2 生成器表达式
(expr for x in iterable if cond)
,语法糖,节省临时列表:
squares = (x*x for x in range(1000000))
4.3 send / throw / close —— 双向通信
def echo():
received = yield 'READY'
while True:
received = yield f'ECHO:{received}'
gen = echo()
next(gen) # 启动到第一个 yield,返回 'READY'
gen.send('hi') # 发送并获取 'ECHO:hi'
gen.throw(ValueError, 'boom') # 抛异常到生成器内部
gen.close() # 触发 GeneratorExit
4.4 yield from —— 委托子生成器
def chain(*iterables):
for it in iterables:
yield from it
等价于:
for it in iterables:
for x in it:
yield x
但 yield from
会自动处理子生成器的 send/throw/return,适用于协程调度器。
4.5 生成器与协程
PEP 342 以后,生成器变成“可暂停的协程”,为 async/await
铺路。
5.三者的关系与区别:一张思维导图
可迭代:只要“能被迭代”,不保证惰性。
├── 序列型:list、str、tuple —— 内存全载。
└── 生成器型:generator、range —— 惰性。
• 迭代器:可迭代的一种,额外实现 __next__
,状态化。
├── 由 iter(obj) 获得。
└── 生成器对象自身就是迭代器。
• 生成器:一种特殊的迭代器,通过编译器自动实现 __iter__/__next__
,支持挂起/恢复栈帧。
6.进阶:自定义可迭代对象与生成器协程
6.1 可迭代对象的多遍遍历
class LineReader:
def __init__(self, path):
self.path = path
def __iter__(self):
with open(self.path) as f:
for line in f:
yield line.rstrip('\n')
每次 for 循环都会新建文件句柄,保证可重入。
6.2 迭代器的惰性切片
itertools.islice(iterable, start, stop, step)
可以在不展开序列的情况下切片。
6.3 协程式任务调度器(简化版)
import heapq, time
def sleep(delay):
end = time.time() + delay
while time.time() < end:
yield
class Scheduler:
def __init__(self):
self.ready = []
def call_later(self, delay, func):
heapq.heappush(self.ready, (time.time()+delay, func))
def run(self):
while self.ready:
when, func = heapq.heappop(self.ready)
delay = max(0, when - time.time())
time.sleep(delay)
for _ in func():
pass # 驱动生成器
该调度器用生成器模拟了“非阻塞 sleep”,是 asyncio 的极简原型。
7.性能、陷阱与调试技巧
7.1 性能对比
• 列表推导 vs 生成器表达式:
[f(x) for x in data]
立即占用 O(n) 内存;
(f(x) for x in data)
占用 O(1)。
• 运行速度:生成器每次 yield 都有函数调用开销,极端情况下比列表慢 10%–30%,但通常被 I/O 掩盖。
7.2 陷阱
• 迭代器只能遍历一次:
it = iter([1,2,3])
list(it) # [1,2,3]
list(it) # []
• 生成器 throw/close 不会自动清理外部资源,需配合 try/finally 或 contextlib.closing
。
• 在生成器里捕获 GeneratorExit
后禁止再 yield,否则抛 RuntimeError
。
7.3 调试技巧
• 使用 inspect.getgeneratorstate(gen)
查看挂起状态。
• 在生成器内部 yield
前后打印日志,或借助 yield (debug_info, real_value)
元组。
• pdb
支持单步调试生成器,命令 n
会跨 yield;用 s
进入子生成器。
8.总结与展望
可迭代、迭代器与生成器构成了 Python 数据管道的核心抽象:
• 可迭代回答“能不能 for”;
• 迭代器回答“如何一个一个拿”;
• 生成器用最小的语法成本,让“惰性 + 状态 + 双向通信”成为日常。
随着 Python 3 引入 async for
、async def
、anext()
,生成器协议进一步演进为“异步迭代协议”。未来,无论是大数据流处理(pandas 2.0 的 Arrow 后端)、HTTP/3 的流式响应,还是 WebAssembly 驱动的浏览器 Python,迭代器思想都将继续发光。