>>>PyPathPython 学习站
首页›核心篇›kp-013
核心篇 · Core lv.2 核心 kp-013

生成器(generator 与 yield)

前置知识:kp-012

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. 常见误区

  1. 调用生成器函数以为得到结果 —— 得到的是"机器"不是"数据";忘 list()/循环消费后一切为空。
  2. 生成器只能遍历一次 —— 协议使然(kp-012);要多次遍历先物化。
  3. 在生成器里 return 值以为调用方能拿到 —— for 循环拿不到 return 值;它藏在 StopIteration.value,显式取值要用 yield from 的完整形态。
  4. 试图 len(g) —— 生成器没有长度(惰性意味着未算完无法知道),需要时先物化或自己计数。

10. 自测题

  1. g = (x for x in range(3)) 后 list(g) 两次调用结果分别是什么?
  2. 生成器如何"记不住执行位置却能恢复"?描述暂停点保存了什么。
  3. yield from sub 与 for x in sub: yield x 的区别?
参考答案
  1. 第一次 [0,1,2],第二次 [](已耗尽)。
  2. 生成器帧在暂停时被整体挂起(局部变量、栈位置都在),恢复时从挂起点继续,而非重新执行函数。
  3. 结果上前者更简洁且能正确传递 StopIteration.value、异常与 send;后者只转发值。

11. 与其他知识点的关系

  • kp-031 asyncio:async def/await 的历史根基。
  • kp-038 itertools:与生成器配合的标准工具箱。
  • kp-011 推导式:生成器表达式。

12. 延伸阅读

  • PEP 255 Simple Generators
  • 《深入理解 Python 特性》生成器与协程章节