Python 迭代器与生成器
可迭代对象提供迭代器;迭代器实现 __next__;生成器用 yield 惰性产出状态机。
#concept
#growing
#dev
#python
#python
[!info] related notes
Python 迭代器与生成器
这个概念为什么出现
for 能遍历文件行、查询结果、无限流,是因为迭代协议,不是因为它们都是 list。生成器让你用同步写法表达惰性流水线,避免一次装入内存。
[!abstract] 一句话理解 可迭代对象通常通过
__iter__提供迭代器;迭代器的__iter__返回自身,__next__产出元素,耗尽时抛 StopIteration。调用含 yield 的生成器函数得到生成器迭代器,不立即执行函数体。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:按批次惰性产出待同步订单 ID
# order_batches_gen.py
# 业务意图:把大列表切成固定批次,惰性产出。
# 教学点:
# - yield 生成器;
# - for 消费迭代器;
# - 耗尽后不可再取。
from typing import Iterator, Sequence
def batched(items: Sequence[str], size: int) -> Iterator[list[str]]:
if size <= 0:
raise ValueError("size must be positive")
for i in range(0, len(items), size):
yield list(items[i : i + size])
def main() -> None:
orders = [f"O{i}" for i in range(1, 6)]
gen = batched(orders, 2)
print(next(gen))
print(next(gen))
print(list(gen)) # 剩余批次
if __name__ == "__main__":
main()
建议运行:
python order_batches_gen.py
期望输出:
['O1', 'O2']
['O3', 'O4']
[['O5']]
结合场景再看三个关注点
- 批次按需产出,但本例先构造了完整的 orders 列表;生成器保留其引用,每批还会创建 list,不代表输入流式读取或总内存恒定。
next与 for 都在推进同一迭代器状态。- 生成器一次性消费;size 校验也要首次推进时才执行,调用 batched(orders, 0) 本身不会立即报错。
核心概念与准确模型
flowchart LR
Iterable -->|"iter()"| Iterator
Iterator -->|"next()"| Item
Iterator -->|"end"| StopIteration
- iter(x) / next(it, default);iter 还支持从索引 0 开始的旧式
__getitem__序列协议,不能仅凭是否定义__iter__判断可迭代。 - 生成器函数、生成器表达式
yield from委托子迭代器
边界情况与反直觉行为
- list 是可迭代也可反复遍历;生成器不行。
- 同步生成器的 return value 成为结束时的 StopIteration.value;不要用显式 raise StopIteration 结束生成器,它逸出生成器体时会转为 RuntimeError。
- break 只停止消费,不保证立即关闭生成器。生成器持有文件等资源且可能提前退出时,需由拥有者显式 close 或使用 contextlib.closing;close 可触发已经进入的 try/finally。
常见误区
[!warning] 常见误区:把生成器当列表返回多次使用 错误理解:变量里装着全部数据。
正确模型:装着“如何继续算”的状态机。
工程实践
- 大文件按行迭代
- 流水线:生成器链接
- 多遍消费可在数据量有限时物化为 list,或重建数据源;list(gen) 会将所有剩余元素装入内存,无限流不能如此处理。
本节总结
迭代协议统一了 for 的世界;生成器是最省事的惰性工具。
自测题
- 可迭代与迭代器差别?
- 为什么
list(gen)后 gen 空了?
参考答案
- 可迭代能提供迭代器;迭代器保持遍历位置并 next。
- 迭代器已耗尽。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Iterator types | 文档 | 协议 |
| Yield expressions | 规范 | yield |