在 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

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

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

更多推荐