人人都会AI编程

生成器函数、yield 关键字、惰性求值机制

更新时间:2026-07-12

生成器是 Python 中一种特殊的迭代器,它让你用函数的写法来实现迭代逻辑,但执行时不会一次性把所有结果算出来,而是用到的时候才计算下一个值。这种“按需计算”的策略就是惰性求值。

生成器函数:用 yield 代替 return

  • 定义生成器的语法和普通函数几乎一样,只是不再用 return 返回所有结果,而是用 yield 逐个“产出”结果。
  • 当函数体中出现 yield 关键字时,该函数就不再是普通函数,而是生成器函数。调用生成器函数并不会执行函数体,而是返回一个生成器对象。

一个最简单的例子:

def count_up_to(n):
    i = 1
    while i <= n:
        yield i
        i += 1

gen = count_up_to(3)      # 这里不会执行函数体,只是创建了一个生成器对象
print(next(gen))          # 输出 1
print(next(gen))          # 输出 2
print(next(gen))          # 输出 3
# 再调用 next() 会抛出 StopIteration 异常

yield 关键字的执行流程
每次调用 next()(或者在 for 循环中每次迭代)时:

  • 生成器函数从头开始执行,或从上次暂停的地方继续执行。
  • 遇到 yield 时,把 yield 后面的值返回给外部调用者,函数状态暂停并保留所有局部变量。
  • 当再次调用 next() 时,函数从暂停处继续往下执行,直到再次遇到 yield,以此类推。
  • 如果函数执行完也没有再遇到 yield,则自动抛出 StopIteration,循环就会正常结束。

这就像一个可以多次“中断再恢复”的函数,非常形象。

惰性求值:省内存、能处理无限序列
“惰性”意味着延迟计算——数据只在真正需要的那一刻才被生成,而不是提前全部算好放在列表里。

  • 处理大文件:比如读取一个几 GB 的日志文件,如果用 f.readlines() 会把全部内容加载到内存,很可能爆内存。而把文件对象当作迭代器(或写一个逐行 yield 的生成器),每次只读入一行处理一行,内存占用只有一行的大小。
  • 无限序列:生成器可以产出无穷无尽的值(比如所有自然数、斐波那契数列),只要外部循环来控制何时停止。如果用具象列表,预先生成无限个值是不可能的。
def infinite_numbers():
    n = 0
    while True:
        yield n
        n += 1
  • 管道式组合:多个生成器可以像管道一样串联,前一个生成器的输出成为后一个生成器的输入。每一层都是按需计算的,整个链条的内存消耗极小。

生成器表达式:更简洁的语法
和列表推导式类似,换成圆括号就成了生成器表达式。它不会立即计算所有元素,而是返回一个生成器对象。

squares = (x * x for x in range(10))   # 生成器对象
print(next(squares))                   # 0
print(next(squares))                   # 1
# 可以直接用在 for 循环中
for sq in (x * x for x in range(5)):
    print(sq)

与列表推导式的区别:

  • [x*x for x in range(1000000)] 会立刻在内存中创建 100 万个元素的列表,可能占用几百 MB 内存。
  • (x*x for x in range(1000000)) 只是一个生成器,内存占用几乎可以忽略不计,计算在迭代时实时进行。

实际应用场景

  • 文件逐行读取与过滤:例如,读取大日志文件,找出包含 “ERROR” 的行,而不必加载整个文件。
  • 数据流水线:读取数据 → 清洗数据 → 转换格式 → 输出结果,每一步都用生成器,整条流水线内存高效。
  • 替代临时列表:在很多只需要遍历一次的场景下,用生成器表达式代替列表推导式可以显著降低内存消耗。例如 sum(x*2 for x in range(1000000)),直接用生成器传给 sum(),无需先创建一个百万元素的列表。

本节关键总结

  • 生成器函数:用 yield 逐个产出值,调用时返回生成器对象,支持 next() 或直接用于 for 循环。
  • yield 执行模型:暂停并保存现场,等待下一次 next() 时恢复执行。
  • 惰性求值:按需生成数据,避免一次性加载所有数据到内存,非常适合处理大数据量和无限序列。
  • 生成器表达式:语法更紧凑的生成器创建方式,场景和列表推导式类似,但内存友好。