在 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 的行为可以概括为三个步骤:

  1. 产出值:将 yield 后面的表达式结果返回给调用者。
  2. 暂停执行:函数当前的执行状态(包括局部变量的值、代码执行位置等)被冻结/保存
  3. 等待唤醒:当下一次调用 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

特性returnyield
返回值返回单值,然后终止函数。返回值,但暂停函数,保留状态。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 将普通函数转换为生成器函数,返回一个迭代器。
  • 机制:实现“暂停 - 恢复”机制,保存函数执行上下文(局部变量、指令指针)。
  • 优势
    1. 内存友好:惰性求值,适合大数据处理。
    2. 逻辑清晰:可以用线性的代码逻辑描述复杂的流式处理或状态机。
    3. 无限可能:支持无限序列。
  • 应用:文件逐行读取、大型数据集处理、协程(Coroutine)、数据流管道、异步编程基础(async/await 的底层原理与生成器密切相关)。
Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐