type
status
date
slug
summary
tags
category
icon
password
📝 高级生成器特性
send(value)
→ 把 value
赋给当前 yield
表达式 → 继续跑字节码直到下一个 yield
或 return
。close()
→ 字节码插入 RAISE_VARARGS GeneratorExit
→ 如果生成器捕获 GeneratorExit
并 return
,则正常结束;否则隐式 StopIteration
。throw()
→ 字节码插入 RAISE_VARARGS
指定异常,其余同 send
。1、生成器的 send(value)
方法
作用:把
value
注入到 当前 yield
表达式 并继续执行到下一个 yield
参数:任意对象
返回值:下一个
yield
产出的值典型异常:
StopIteration
1.1、概念
send(value)
把 外部数据 通过 yield
表达式 注入 到生成器内部,并立即恢复生成器执行,直至下一个 yield
或 StopIteration
。1.2、基本语法
1.3、运行步骤逐帧跟踪
行号 | 生成器代码 | 外部调用 | 生成器状态 | 返回值 |
1 | received = yield 1 | next(gen) | 停在 yield 1 | 1 |
2 | print(received) | gen.send("Hello") | 继续执行 | None |
3 | yield 2 | 同上 | 停在 yield 2 | 2 |
4 | 函数结束 | 同上 | 抛出 StopIteration | \- |
1.4、send()
与 next()
的区别
方法 | 是否注入值 | 首次启动 | 典型用途 |
next(gen) | 否 | ✅ | 仅仅推进 |
gen.send(None) | 否 | ✅ | 同 next |
gen.send(v) | 是 | ❌ | 双向通信 |
1.5、异步场景——“协程雏形”
- 在
async/await
出现前,生成器 +send()
就是 协程 的底层实现:
yield
暂停 I/O;事件循环用
send(result)
把 I/O 结果送回生成器,从而“异步”继续执行 。1.6、完整示例:计数协程 + 外部注入
1.7、常见陷阱
- 首次忘记
next()
直接
gen.send(123)
会抛 TypeError
,因为生成器尚未启动。- 在
yield
前return
send()
之后若立即return
,将抛出 StopIteration(value)
,value
即 return
的值。- 循环变量闭包
在列表推导式里使用
send()
时要注意延迟绑定问题,与 lambda 闭包类似。2、生成器的 close()
方法
作用:立即在生成器内部抛出
GeneratorExit
,可做清理参数:无
返回值:
None
典型异常:若生成器
yield
后未捕获,则隐式 StopIteration
3、生成器的 throw(type[, value[, traceback]])
方法
作用:在生成器内部抛出指定异常并继续执行
参数:异常类或实例
返回值:下一个
yield
产出的值典型异常:同
send
,但会抛出你给定的异常4、生成器表达式
5、yield from
委托
5.1、基本语法
5.2、工作原理
- 当执行到
yield from iterable
时,Python 会: - 遍历
iterable
中的每个元素。 - 将每个元素依次
yield
出去。 - 如果
iterable
是一个生成器,那么yield from
会捕获生成器的StopIteration
异常(该异常在生成器耗尽时抛出),并正常结束外层生成器。
5.3、示例
5.4、高级用法
5.5、与 yield
的区别
yield
:用于从生成器中返回一个值,并在下一次调用 next()
时从上次停止的地方继续执行。yield from
:用于将一个生成器或可迭代对象的输出转发到另一个生成器。它允许生成器委托给另一个生成器,从而简化代码。📝 生成器的实际应用场景
1、处理大型文件/数据集
1.1、概念
生成器把“一次性读入”变成“按需流式读取”,只保留当前一行(或一块)在内存,从而 O(1) 内存 处理 GB 级文件。
1.2、内存模型对比
方式 | 内存峰值 | 说明 |
f.read() 或 list(f) | 文件大小 | 内容全部加载 |
for line in f: | 一行 | 内建缓冲区 |
yield 生成器 | 一行 + 对象 | 手动控制块大小,可再压缩 |
1.3、三种典型场景
- 逐行文本
- 分块二进制
- 数据库 / 网络流
1.4、逐行示例:统计 1 GB 日志里 ERROR 行数
1.5、分块二进制:SHA-256 大文件
1.6、性能 & 高级技巧
技巧 | 代码片段 | 效果 |
缓冲大小 | f.read(size) | 调整 I/O 块大小 |
惰性转换 | (int(x) for x in lines) | 零拷贝转换 |
多文件链 | yield from file_gen | 无缝拼接 |
并发 | yield from asyncio_stream() | 与协程配合 |
1.7、完整实战:CSV → 清洗 → 入库(百万行)
2、无限序列
2.1、概念
把
while True:
和 yield
写在一起,就能得到一个按需生产、永不枯竭的数据流;外部只需按需 next()
或 for x in gen:
拿值,内存始终 O(1)。2.2、最小骨架
2.3、内存优势对比
方式 | 存储 | 内存峰值 |
list(range(10**8)) | 一次性加载 | ≈ 3 GB |
无限 yield | 按需生产 | ≈ 几十字节 |
2.4、4 类经典无限序列
- 自然数
- 斐波那契
- 等差 / 等比序列
- 素数流(埃拉托斯特尼筛)
2.5、截取技巧
需求 | 工具 | 示例 |
前 N 个 | itertools.islice | list(islice(gen, 10)) |
满足条件的前 N 个 | itertools.takewhile | list(takewhile(lambda x: x < 100, gen)) |
跳过前 N 个 | itertools.dropwhile | list(dropwhile(lambda x: x < 100, gen)) |
2.6、实战案例:无限素数流 → 前 10 个 4k+1 型素数
3、数据管道
3.1、一句话定义
“生成器数据管道”就是把一连串生成器像 Unix 管道一样串起来:上游不断
yield
,下游不断 for x in upstream
或 yield from
,数据像水流一样逐层传递,内存占用极低。3.2、最小骨架
3.3、运行步骤逐帧图
数据按需生产,下游用到多少就生产多少。
任何阶段随时
break
,上游立即停止,零浪费。3.4、三种连接方式对比
连接方式 | 语法 | 特点 |
普通 for | for x in g: yield f(x) | 最通用,可额外逻辑 |
yield from | yield from g | 一行代理,无额外开销 |
生成器表达式 | (f(x) for x in g) | 极简,但只能单行表达式 |
3.5、性能对比
方式 | 内存峰值 | 时间 |
列表推导式 | 全部加载 | 高 |
生成器管道 | O(1) | 低 |
4、状态机实现
生成器函数的 执行位置 + 局部变量 被解释器保存在 frame + cell 里,每次
next()
/ send()
从上次暂停处继续跑,本质就是 “带记忆的 goto”。📝 生成器与协程
生成器在 Python 3.5+ 中被扩展为协程,用于异步编程。虽然生成器和协程在底层共享很多机制,但它们的用途和语义有所不同。