ZetCode

Python Yield 关键字

上次修改时间:2025 年 2 月 25 日

Python 中的 yield 关键字将函数转换为生成器,生成器可以暂停和恢复执行。与结束函数的 return 不同,yield 增量式地传递值。这使得可以高效地处理大型数据集。本指南探讨了生成器的创建、迭代和高级技术。

生成器在 yield 之间保留其状态。调用生成器函数会返回一个迭代器,并且每个 next 调用都会从上一个 yield 处恢复执行。这种惰性求值可以最大限度地减少内存使用,使其成为迭代任务的理想选择。

基本生成器函数

此示例演示了一个生成几个值的基本生成器。

simple_gen.py
def number_generator():
    yield 10
    yield 20
    yield 30

gen = number_generator()
print(next(gen))  # Output: 10
print(next(gen))  # Output: 20
print(next(gen))  # Output: 30

生成器按需生成值。每个 next 调用都会从上一个 yield 停止的地方继续,传递下一个值。在最后一个 yield 之后,它会引发 StopIteration

生成斐波那契数列

此生成器生成高达给定限制的斐波那契数。

fib_gen.py
def fibonacci(limit):
    a, b = 0, 1
    while a < limit:
        yield a
        a, b = b, a + b

for num in fibonacci(50):
    print(num, end=' ')  # Output: 0 1 1 2 3 5 8 13 21 34

值仅在请求时计算,避免了将完整序列存储在内存中的需要。这种效率适合大型或无界范围。

内存效率比较

这将比较列表和生成器之间的内存使用情况。

memory_compare.py
import sys

def list_squares(n):
    return [x**2 for x in range(n)]

def gen_squares(n):
    for x in range(n):
        yield x**2

print(sys.getsizeof(list_squares(500)))  # ~2300 bytes
print(sys.getsizeof(gen_squares(500)))   # ~112 bytes

生成器最小的内存占用源于按需生成值,这与列表不同,后者会预先存储所有内容。

向生成器发送值

生成器可以通过 send 接受值以进行交互式使用。

send_gen.py
def accumulator():
    total = 0
    while True:
        value = yield
        total += value
        yield total

gen = accumulator()
next(gen)  # Prime the generator
gen.send(4)
print(next(gen))  # Output: 4
gen.send(6)
print(next(gen))  # Output: 10

使用 send,将值馈送到生成器,从而更新其状态。这种双向通信支持动态计算。

Yield 表达式

这使用 yield 作为 send 的表达式。

yield_expr.py
def counter():

    count = 0
    while True:
        increment = yield count
        count += increment

gen = counter()
next(gen)  # Start it
print(gen.send(2))  # Output: 2
print(gen.send(3))  # Output: 5

生成器通过 send 接收增量并产生运行总计,从而展示了交互式状态管理。

处理异常

生成器使用 throwclose 处理异常。

gen_exception.py
def safe_gen():
    try:
        while True:
            yield "Active"
    except ValueError:
        yield "Recovered"

gen = safe_gen()
print(next(gen))           # Output: Active
print(gen.throw(ValueError))  # Output: Recovered
gen.close()                # Stops the generator

throw 方法会触发生成器内部的异常,这些异常可以被捕获和处理。close 会干净地终止它。

使用 Yield 的无限序列

这会生成一个无限的偶数序列。

infinite_gen.py
def even_numbers():
    n = 0
    while True:
        yield n
        n += 2

evens = even_numbers()
for _ in range(5):
    print(next(evens), end=' ')  # Output: 0 2 4 6 8

生成器会无限地生成偶数,并按需产生每个值。循环或限制器控制消耗的数量。

带有子生成器的 Yield From

yield from 将产生操作委托给另一个生成器。

yield_from.py
def sub_gen():
    yield 1
    yield 2
    yield 3

def main_gen():
    yield from sub_gen()
    yield 4

gen = main_gen()
print(list(gen))  # Output: [1, 2, 3, 4]

yield from 语法简化了对 sub_gen 的委托,直接产生其值,然后进行额外的 yield。

下一个脚本演示了使用 yield from 在生成器之间委派任务。子生成器 process_file_lines 从文件中读取行,并通过在文件丢失时产生错误消息来优雅地处理错误。

yield_from2.py
def process_file_lines(filename):
    """Subgenerator that yields lines from a specific file."""

    try:
        with open(filename, 'r') as file:
            for line in file:
                yield line.strip()
    except FileNotFoundError:
        yield f"Error: File '{filename}' not found"

def process_multiple_files(file_list):
    """Main generator that delegates to process_file_lines for each file."""

    for filename in file_list:
        print(f"Processing {filename}:")
        yield from process_file_lines(filename)
        yield "---"  # Separator between files

def main():

    files_to_process = ["users.txt", "orders.txt", "missing.txt"]    
    file_processor = process_multiple_files(files_to_process)
    
    # Process and display results
    for result in file_processor:
        print(result)

if __name__ == "__main__":
    main()

主生成器 process_multiple_files 处理文件列表,并通过 yield from 委托给 process_file_lines 进行行读取。它在文件之间添加分隔符以提高可读性,并保持逻辑模块化和清晰。

main 中,程序使用示例文件列表来展示成功的处理和错误处理。这种方法突出了 yield from 如何简化代码并增强可维护性,尤其是在涉及嵌套任务的工作流程中。

使用 Yield 读取文件

这使用生成器逐行读取文件。

file_gen.py
def read_file(filename):
    with open(filename, 'r') as f:
        yield from f

for line in read_file('example.txt'):
    print(line.strip())

使用 yield from,生成器会流式传输文件行,而无需将整个文件加载到内存中,从而提高了效率。

扁平化嵌套列表

这使用 yield from 递归地扁平化嵌套列表。

flatten_gen.py
def flatten(items):
    for item in items:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item

nested = [1, [2, 3], [4, [5]]]
print(list(flatten(nested)))  # Output: [1, 2, 3, 4, 5]

yield from 的递归使用会解包嵌套列表,并在平面序列中单独产生每个元素。

随机数据生成器

这会无限地生成随机整数。

random_gen.py
import random

def random_nums(min_val, max_val):
    while True:
        yield random.randint(min_val, max_val)

rands = random_nums(1, 10)
for _ in range(3):
    print(next(rands))  # e.g., Output: 7 3 9

生成器会无限地产生随机数,这对于测试或模拟很有用,并且仅在需要时才生成值。

使用 Yield 的最佳实践

来源

Python Yield 表达式文档

本指南介绍了 yield 关键字在创建高效生成器中的作用,包括 yield from 等高级功能,用于委托和迭代。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。到目前为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程