python yield
·
在 Python 中,yield 是一个用于定义**生成器函数(Generator Function)**的关键字。它的出现彻底改变了我们处理数据序列的方式,特别是面对大数据量或无限序列时。
简单来说,yield 让函数具备了“暂停”和“恢复”的能力。
以下是关于 yield 的详细讲解,涵盖核心概念、工作原理、与普通函数的区别、高级用法以及实际应用场景。
1. 核心概念:什么是生成器?
当一个函数中包含 yield 关键字时,它就不再是一个普通的函数,而是一个生成器函数。
- 调用普通函数:函数立即执行,遇到
return返回结果并彻底结束,局部变量被销毁。 - 调用生成器函数:函数不会立即执行,而是返回一个生成器对象(Generator Object)。这个对象是一个迭代器,你可以随时让它“运行一步”,它会执行到下一个
yield处暂停,并返回一个值。
直观对比
# 普通函数
def normal_func():
return [1, 2, 3]
result = normal_func()
# 立即执行,返回整个列表 [1, 2, 3],占用内存存储所有元素
# 生成器函数
def generator_func():
yield 1
yield 2
yield 3
gen = generator_func()
# 不执行函数体!只返回一个生成器对象 <generator object generator_func at 0x...>
# 只有当你请求数据时,它才开始计算
2. yield 的工作原理:暂停与状态保持
yield 的行为可以概括为三个步骤:
- 产出值:将
yield后面的表达式结果返回给调用者。 - 暂停执行:函数当前的执行状态(包括局部变量的值、代码执行位置等)被冻结/保存。
- 等待唤醒:当下一次调用
next()或在for循环中请求下一个值时,函数从上次暂停的地方继续执行,直到遇到下一个yield或函数结束。
代码演示执行流程
def simple_generator():
print("Step 1: 开始执行")
val1 = yield "A" # 第一次暂停,返回 "A"
print(f"Step 2: 接收到值 {val1}, 继续执行")
val2 = yield "B" # 第二次暂停,返回 "B"
print(f"Step 3: 接收到值 {val2}, 继续执行")
yield "C" # 第三次暂停,返回 "C"
print("Step 4: 函数结束")
# 创建生成器
g = simple_generator()
# 驱动生成器
print("--- 启动 ---")
print(next(g)) # 输出: Step 1... -> A (暂停在第一个 yield)
print("\n--- 继续 ---")
print(next(g)) # 输出: Step 2... (如果上面send了值这里会显示) -> B (暂停在第二个 yield)
# 注意:上面的 next(g) 只是恢复执行,没有传值,所以 val1 是 None
print("\n--- 结束 ---")
print(next(g)) # 输出: Step 3... -> C (暂停在第三个 yield)
# 再次调用会抛出 StopIteration 异常,因为函数已运行完毕
3. yield vs return
| 特性 | return |
yield |
|---|---|---|
| 返回值 | 返回单值,然后终止函数。 | 返回值,但暂停函数,保留状态。yield下面的代码等待下一次执行 |
| 多次调用 | 函数结束后,再次调用需重新从头开始执行。 | 可以从暂停处多次恢复执行,产生一系列值。 |
| 内存占用 | 如果需要返回大量数据,通常需构建完整列表,占用大内存。 | 惰性计算(Lazy Evaluation),每次只生成一个值,极度节省内存。 |
| 函数类型 | 普通函数。 | 生成器函数。 |
4. 为什么使用 yield?(核心优势)
A. 节省内存(处理大数据)
如果你需要处理 100 万个数字,使用列表会一次性占用大量内存。使用生成器则只占用常数级内存。
# ❌ 糟糕的做法:一次性加载所有数据到内存
def get_squares_list(n):
result = []
for i in range(n):
result.append(i * i)
return result # 返回一个大列表
# ✅ 推荐的做法:按需生成
def get_squares_gen(n):
for i in range(n):
yield i * i # 每次只算一个数
# 测试内存差异
import sys
n = 1000000
list_mem = sys.getsizeof(get_squares_list(n))
gen_mem = sys.getsizeof(get_squares_gen(n))
print(f"列表占用: {list_mem} 字节") # 几 MB 甚至更多
print(f"生成器占用: {gen_mem} 字节") # 仅约 100 多字节(对象本身大小)
B. 处理无限序列
由于生成器是按需计算的,它可以表示无限长的序列,而不会导致内存溢出。
def fibonacci():
a, b = 0, 1
while True: # 无限循环
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib), end=" ")
# 输出: 0 1 1 2 3 5 8 13 21 34
# 程序不会崩溃,因为它从未尝试生成“所有”斐波那契数
C. 构建数据管道
生成器可以像流水线一样串联起来,数据在处理过程中流动,而不是静态存储。
def read_data():
for i in range(5):
yield i
def filter_even(data):
for x in data:
if x % 2 == 0:
yield x
def square(data):
for x in data:
yield x * x
# 管道组合
pipeline = square(filter_even(read_data()))
'''
list(pipeline) 会for循环pipeline,将拿出的值放到列表中,for循环的原理是使用next()方法取值,所以此时触发了 square() 的生成器取值
square函数内部又是for循环,data参数是 filter_even() 的生成器对象,进而又触发了 filter_even() 的生成器取值 。。。。。。
'''
print(list(pipeline)) # 输出: [0, 4, 16]
# 数据流:0 -> (是偶数) -> 0 -> 0; 1 -> (跳过); 2 -> (是偶数) -> 2 -> 4 ...
5. 进阶用法:双向通信 (send) 与 yield from
A. send(value):向生成器内部传值
yield 不仅可以产出值,还可以接收值。当生成器暂停在 yield 表达式时,调用 gen.send(val) 可以恢复执行,并且 val 会成为 yield 表达式的返回值。
def accumulator():
total = 0
while True:
# 初始时 yield 返回 None,等待 send 传入值
# 下次运行时,received 就是 send 传入的值
received = yield total
if received is None:
break
total += received
acc = accumulator()
next(acc) # 启动生成器,运行到 yield total (total=0),返回 0
print(acc.send(10)) # 发送 10,total 变为 10,返回 10
print(acc.send(20)) # 发送 20,total 变为 30,返回 30
print(acc.send(5)) # 发送 5,total 变为 35,返回 35
注意:第一次启动生成器必须用 next() 或 send(None),因为此时还没有停在 yield 处接收值。
B. yield from:简化嵌套生成器
在 Python 3.3+ 中,yield from 用于委托另一个可迭代对象(如另一个生成器),避免写繁琐的 for 循环。
def sub_gen():
yield "A"
yield "B"
def main_gen():
yield "Start"
yield from sub_gen() # 等价于 for x in sub_gen(): yield x
yield "End"
print(list(main_gen()))
# 输出: ['Start', 'A', 'B', 'End']
6. 生成器表达式
除了定义函数,还可以使用类似列表推导式的语法快速创建生成器,只需将 [] 改为 ()。
# 列表推导式 (立即生成列表)
squares_list = [x*x for x in range(10)]
# 生成器表达式 (延迟生成)
squares_gen = (x*x for x in range(10))
print(type(squares_gen)) # <class 'generator'>
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1
7. 总结
- 本质:
yield将普通函数转换为生成器函数,返回一个迭代器。 - 机制:实现“暂停 - 恢复”机制,保存函数执行上下文(局部变量、指令指针)。
- 优势:
- 内存友好:惰性求值,适合大数据处理。
- 逻辑清晰:可以用线性的代码逻辑描述复杂的流式处理或状态机。
- 无限可能:支持无限序列。
- 应用:文件逐行读取、大型数据集处理、协程(Coroutine)、数据流管道、异步编程基础(
async/await的底层原理与生成器密切相关)。
更多推荐



所有评论(0)