Python深度解析:Yield, Return与Yield From的时空魔法
一、引言:打破"一次性"函数的诅咒
普通函数有一个致命特征——一次性。执行到底,return 结果,销毁所有局部变量。人死灯灭,不留痕迹。
但有些场景需要函数"记住"上次执行到哪里了。比如遍历一个大文件,你不想一次性读入内存,而是读一行、处理一行、再读一行。这就需要函数拥有"记忆"——生成器应运而生。
生成器让函数从"单向流水线"变成了"可暂停的状态机"。
二、Yield:时间的暂停与状态的冻结
2.1 核心机制
当代码执行到 yield 时,函数并没有结束,而是"挂起"了:
- 保存当前的执行位置和所有局部变量
- 产出一个值给调用者
- 交还控制权,等待下次被唤醒
1 | def counter(n): |
输出:
1 | 步骤1:调用 next() |
关键点:局部变量 i 在多次调用之间依然存在且值被保留。这就是生成器的"记忆"——它保存了整个栈帧。
2.2 反直觉的关键
**调用生成器函数只是创建了一个迭代器,并没有执行代码。**只有 next() 或 send() 才会触发执行。这是很多初学者的误区。
1 | def my_gen(): |
三、Return:生成器的终结与异常
3.1 Return 的双重身份
在普通函数中,return 返回数据并结束。在生成器中,return 的语义完全不同:
return(无值):等同于raise StopIteration,标志着迭代的正式结束return value(有值):在 Python 3.3+ 中,触发StopIteration异常,并将value赋值给异常的value属性
1 | def gen_with_return(): |
3.2 重要澄清
生成器的 return 值不会被 next() 直接获取,它被藏在 StopIteration 异常里。手动捕获这个值很麻烦,它的主要用途是配合 yield from——这是获取生成器 return 值的唯一优雅方式。
四、Yield From:数据管道的无缝对接
4.1 痛点场景
没有 yield from 时,委托给子生成器需要写冗余的循环:
1 | def sub_gen(): |
如果不用 yield from,等价写法是:
1 | def main_gen_manual(): |
4.2 语法糖与代理
yield from 不仅仅是语法糖,它是一个透明通道:
- 数据从子生成器流向调用者:子生成器 yield 的值直接传递给调用者
- 异常和 send 值从调用者流向子生成器:调用者
send()的值直接传给子生成器 - 自动获取返回值:
result = yield from sub_gen()优雅地拿到了子生成器的return值
4.3 获取返回值
1 | def accumulator(): |
五、深度对比:Return vs Yield
| 维度 | Return | Yield |
|---|---|---|
| 执行流 | 终止函数 | 暂停函数 |
| 状态 | 销毁栈帧 | 保留栈帧 |
| 返回值 | 返回最终结果 | 产出中间结果 |
| 调用次数 | 一次 | 多次 |
| 函数类型 | 普通函数 | 生成器函数 |
| 内存 | 每次调用重新分配 | 栈帧持续存在 |
六、实战演练:协程的雏形
send() 方法不仅能唤醒生成器,还可以把数据"发送"进生成器内部,赋值给 yield 左边的变量:
1 | def echo(): |
这就是协程的雏形——生成器不仅能产出数据,还能接收数据。yield 既是出口也是入口,形成了一个双向通信通道。
七、总结
三个核心概念:
- Yield 是暂停,不是返回——函数冻结状态,等待下次唤醒,局部变量不会丢失
- Return 在生成器中是终结信号——它触发
StopIteration,返回值藏在异常里,主要服务于yield from - Yield From 是透明管道——在调用者和子生成器之间建立双向通道,自动传递数据和异常,优雅获取返回值
生成器的本质是状态机。每次 yield 是一个状态节点,每次 next() 或 send() 是一次状态转移。理解了这一点,yield 不再神秘,协程也不再遥远。
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.

