文章目录

Python 生成器:yield 关键字与迭代器

发布于 2026-04-03 06:12:52 · 浏览 6 次 · 评论 0 条

Python 生成器:yield 关键字与迭代器

Python 中的 yield 关键字用于定义生成器函数,它能让你像写普通函数一样创建一个惰性求值的迭代器。这种机制在处理大量数据或无限序列时特别有用,因为它不会一次性将所有数据加载到内存中。


理解生成器的基本原理

  1. 编写一个包含 yield 的函数:

    def count_up_to(max):
        count = 1
        while count <= max:
            yield count
            count += 1
  2. 调用该函数时,不会立即执行函数体,而是返回一个生成器对象

    gen = count_up_to(3)
    print(type(gen))  # <class 'generator'>
  3. 使用 next() 函数从生成器中逐个获取值:

    print(next(gen))  # 输出: 1
    print(next(gen))  # 输出: 2
    print(next(gen))  # 输出: 3
    # 再次调用会抛出 StopIteration 异常
  4. 在 for 循环中直接使用生成器,循环会自动处理 StopIteration

    for num in count_up_to(5):
        print(num)
    # 输出: 1 2 3 4 5

生成器 vs 普通列表:内存效率对比

考虑一个需要生成一百万个整数的场景:

  • 普通列表会一次性分配全部内存:

    def make_list(n):
        return [i for i in range(n)]
  • 生成器只在需要时计算下一个值:

    def make_generator(n):
        for i in range(n):
            yield i

你可以通过以下方式验证内存差异(需安装 memory_profiler):

pip install memory-profiler

然后运行:

from memory_profiler import profile

@profile
def test_list():
    big_list = [i for i in range(1000000)]
    return sum(big_list)

@profile
def test_generator():
    big_gen = (i for i in range(1000000))
    return sum(big_gen)

结果会显示生成器版本占用内存显著更少。


使用 send() 方法向生成器传递数据

生成器不仅能产出数据,还能接收外部输入:

  1. 定义一个能接收值的生成器:

    def echo():
        while True:
            received = yield
            print(f"收到: {received}")
  2. 启动生成器并发送数据:

    e = echo()
    next(e)  # 必须先执行一次 next() 到第一个 yield
    e.send("你好")
    e.send("世界")
  3. 结合产出与接收实现双向通信:

    def accumulator():
        total = 0
        while True:
            value = yield total
            if value is not None:
                total += value
  4. 使用这个累加器:

    acc = accumulator()
    next(acc)  # 启动到第一个 yield
    print(acc.send(10))  # 输出: 10
    print(acc.send(20))  # 输出: 30

生成器表达式:简洁的语法糖

除了函数形式,Python 还支持类似列表推导式的生成器表达式

  1. 将列表推导式的方括号 [...] 改为圆括号 (...)

    squares = (x**2 for x in range(10))
  2. 验证其类型和行为

    print(type(squares))  # <class 'generator'>
    print(list(squares))  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  3. 注意:生成器只能遍历一次,再次转换为列表会得到空结果:

    print(list(squares))  # []

常见应用场景

场景 实现方式 优势
大文件逐行处理 def read_lines(filename):<br> with open(filename) as f:<br> for line in f:<br> yield line.strip() 避免将整个文件载入内存
无限序列生成 def fibonacci():<br> a, b = 0, 1<br> while True:<br> yield a<br> a, b = b, a + b 可按需获取任意长度序列
数据管道处理 data = (process(x) for x in source if filter(x)) 链式操作,内存高效

注意事项与陷阱

  1. 不要混淆生成器表达式与元组:

    # 这是生成器
    gen = (x for x in range(3))
    # 这是元组(需要逗号)
    tup = (1,)
  2. 避免在生成器中使用可变默认参数,因为生成器状态会保留:

    # 危险示例
    def bad_example(lst=[]):
        lst.append(1)
        yield lst
  3. 显式关闭不再需要的生成器以释放资源:

    gen = some_generator()
    try:
        for item in gen:
            if some_condition:
                break
    finally:
        gen.close()  # 触发 GeneratorExit 异常
  4. 使用 itertools 模块扩展生成器功能:

    import itertools
    # 重复生成器多次
    repeated = itertools.chain.from_iterable(
        itertools.repeat(count_up_to(3), 2)
    )

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文