Python 迭代器与生成器
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
生成器让你「边算边给」,不必先建一个完整列表。处理大文件、流数据、无限序列时靠它。
环境准备
示例在 Python 3.13 下验证,标准库即可。
可迭代对象与迭代器
- 可迭代对象(iterable):能被
for遍历,实现__iter__。列表、字符串、字典、集合、文件对象都是。 - 迭代器(iterator):实现
__iter__和__next__,记住当前位置,只能前进。
items = [1, 2, 3]
it = iter(items) # 拿到迭代器
next(it) # 1
next(it) # 2
next(it) # 3
next(it) # StopIteration 异常
for 循环做的事就是:拿迭代器 → 反复 next() → 捕获 StopIteration。
for x in items:
print(x)
# 等价于
it = iter(items)
while True:
try:
x = next(it)
except StopIteration:
break
print(x)
列表可以反复遍历(每次 iter() 返回新迭代器),迭代器本身只能用一次。
生成器函数:yield
函数里有 yield,调用它不再执行函数体,而是返回一个生成器对象:
def count_up(n: int):
for i in range(n):
print(f"产出 {i}")
yield i
gen = count_up(3) # 没输出,函数体还没执行
next(gen) # 产出 0 → 返回 0
next(gen) # 产出 1 → 返回 1
list(gen) # 产出 2 → [2]
执行流程:每次 next() 走到 yield 就暂停并保留局部变量,下次从暂停处继续;函数结束(或 return)时抛 StopIteration。
这就是「惰性」的来源——值在需要时才计算。
为什么用生成器
对比读一个大文件:
# 一次读入:文件 2GB 就占 2GB 内存
lines = Path("huge.log").read_text(encoding="utf-8").splitlines()
errors = [line for line in lines if "ERROR" in line]
# 生成器:内存中只有当前一行
def read_lines(path: str):
with open(path, encoding="utf-8") as f:
for line in f:
yield line.rstrip("\n")
errors = (line for line in read_lines("huge.log") if "ERROR" in line)
for line in errors:
print(line)
生成器表达式 (...) 与生成器函数可以串成管道,逐条处理,内存恒定。
yield from
把「遍历另一个可迭代对象并逐个产出」简写成一行,还能转发 send:
def chain(*iterables):
for it in iterables:
yield from it # 等价于 for x in it: yield x
list(chain([1, 2], [3])) # [1, 2, 3]
递归遍历目录树很自然:
def walk(path: Path):
for child in path.iterdir():
if child.is_dir():
yield from walk(child)
else:
yield child
send、throw 与 close
生成器可以双向通信:
def accumulator():
total = 0
while True:
value = yield total # yield 表达式的值来自 send
if value is None:
break
total += value
acc = accumulator()
next(acc) # 启动,产出 0(必须先启动)
acc.send(10) # 10
acc.send(5) # 15
acc.close()
gen.send(value):把值送进生成器,作为yield表达式的结果。gen.throw(Exc):在暂停处抛出异常。gen.close():结束生成器(内部抛GeneratorExit)。
日常代码很少用 send,它主要出现在协程与框架的底层。
自己写迭代器
需要复用、可多次遍历、或要额外方法时,写成类:
class Range2:
def __init__(self, stop: int):
self.stop = stop
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current >= self.stop:
raise StopIteration
value = self.current
self.current += 1
return value
常见错误与注意事项
1. 生成器只能用一次
gen = (x * 2 for x in [1, 2])
list(gen) # [2, 4]
list(gen) # [],已耗尽
要多次遍历就写成函数,每次调用返回新生成器。
2. 把 return 的值当成产出
生成器函数里的 return value 不会产出值,值会放进 StopIteration.value(通常由框架取出)。要产出就用 yield。
3. 生成器里的异常被延迟
函数体只在迭代时执行,所以 open 失败之类的错误会在第一次 next() 时才抛,栈看起来离定义处很远。调试时注意。
4. 生成器没关闭导致资源泄漏
生成器里打开的文件、连接,如果没迭代完就不会执行到清理代码。用 with contextlib.closing(gen) 或确保迭代完整。
5. 过度使用生成器
数据量小时列表更快也更直观,惰性只在数据大或无限时才有意义。
小结
- 可迭代对象能反复遍历,迭代器只能前进一次。
yield让函数在产出时暂停,保留状态,实现惰性计算。- 用生成器管道处理大文件,内存只占一行。
yield from委托给另一个可迭代对象;send用于双向通信(高级场景)。