核心篇 · Core
lv.2 核心
kp-013
生成器(generator 与 yield)
1. 一句话定义
含 yield 的函数是生成器函数:调用它不执行函数体,而是返回一个惰性迭代器,每次 next() 才执行到下一个 yield 并暂停——把"生产数据的循环"变成"按需供数据的管道"。
2. 为什么重要
生成器同时解决三件事:省内存(不构造中间列表)、可组合(生成器接生成器成流水线)、可控(send/close/throw 使其成为协程前身,kp-031 的地基)。读第三方代码时,yield 出现频率极高。
3. 前置知识
kp-012(迭代器协议——生成器就是"自动实现的迭代器")。
4. 核心概念
- 生成器函数:函数体含
yield;调用返回生成器对象(也是 iterator)。 - 暂停与恢复:
yield处暂停,状态(局部变量、执行位置)完整保留。 - 生成器表达式:
(x*x for x in it),匿名生成器(kp-011)。 yield from:把子生成器的产出全部转发(委托)。return:在生成器里意味着结束(值进StopIteration.value)。
5. 原理与机制
python
def read_large(path):
with open(path, encoding="utf-8") as f:
for line in f: # 文件本身也是惰性迭代
line = line.strip()
if line: # 过滤
yield line
# 流水线:生成器接生成器,全程只有一行在内存里
lines = read_large("access.log")
errors = (l for l in lines if "ERROR" in l)
first10 = [l for _, l in zip(range(10), errors)] # 只取 10 条生成器与等价列表的内存差异:列表版需先读全文件再过滤再切片(三份中间数据),生成器版逐行流过。
6. 关键事实(模型/图示)
text
next(g) ──► 执行到 yield x,返回 x,暂停
next(g) ──► 从暂停处继续执行
耗尽/return ──► StopIteration
[表达式版] list:一次性全部物化 生成器:按需逐个物化7. 直观类比
列表是先把整锅汤做好端上桌;生成器是汤锅直接端上来:客人要一碗盛一碗,锅还在灶上慢慢熬。人少时不浪费,人多时也不会更慢。
8. 实例与案例
python
# 无限序列(列表做不到)
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
from itertools import islice
list(islice(fib(), 8)) # [0, 1, 1, 2, 3, 5, 8, 13]
# yield from 委托
def all_pages(pages):
for p in pages:
yield from fetch(p) # 逐条转发,调用方无感9. 常见误区
- 调用生成器函数以为得到结果 —— 得到的是"机器"不是"数据";忘
list()/循环消费后一切为空。 - 生成器只能遍历一次 —— 协议使然(kp-012);要多次遍历先物化。
- 在生成器里 return 值以为调用方能拿到 ——
for循环拿不到 return 值;它藏在StopIteration.value,显式取值要用 yield from 的完整形态。 - 试图
len(g)—— 生成器没有长度(惰性意味着未算完无法知道),需要时先物化或自己计数。
10. 自测题
g = (x for x in range(3))后list(g)两次调用结果分别是什么?- 生成器如何"记不住执行位置却能恢复"?描述暂停点保存了什么。
yield from sub与for x in sub: yield x的区别?
参考答案
- 第一次
[0,1,2],第二次[](已耗尽)。 - 生成器帧在暂停时被整体挂起(局部变量、栈位置都在),恢复时从挂起点继续,而非重新执行函数。
- 结果上前者更简洁且能正确传递 StopIteration.value、异常与 send;后者只转发值。
11. 与其他知识点的关系
12. 延伸阅读
- PEP 255 Simple Generators
- 《深入理解 Python 特性》生成器与协程章节