Python生成器(Generator)教程

什么是生成器

生成器(Generator)是Python中一种特殊的懒加载迭代器。它不会像列表那样一次性把所有值都计算出来塞进内存,而是等你真正要用下一个值时,才“临时开工”把它算出来。这种按需计算的特性,让生成器在处理超大数据集、无限序列等场景时,能直接把内存占用从“灾难级”压到“友好级”。

生成器的核心优势可以浓缩为三点:

  • 极致省内存:只保存当前执行上下文,不存储历史值,也无需预计算未来值
  • 状态保持强:执行到yield会自动暂停,下一次从断点处续跑,局部变量的值都还在
  • 代码超简洁:相比手写一个完整的迭代器类(要实现__iter____next__),生成器可以让代码量减少一大半

创建生成器的两种方法

1. 生成器表达式:最轻量的“懒计算”配方

生成器表达式的语法和列表推导式极其相似,唯一的区别就是把方括号[]换成圆括号()

# 列表推导式 —— 一次性生成10个平方数,全部占内存
list_comp = [x**2 for x in range(10)]
print(type(list_comp))  # <class 'list'>
print(list_comp)        # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 生成器表达式 —— 只保存了计算逻辑,什么都没真正算出
gen_exp = (x**2 for x in range(10))
print(type(gen_exp))    # <class 'generator'>

💡 小贴士:生成器表达式适合不需要复用、逻辑简单的数据转换和过滤场景。如果逻辑超过一行,或者需要多次暂停/恢复,推荐使用生成器函数。

2. 生成器函数:yield关键字是灵魂

普通函数一旦执行到return就会彻底退出,所有局部变量都会被销毁。但只要函数体里出现了yield关键字,Python解释器就会把它识别为生成器函数——调用它时不会立即执行函数体,而是返回一个生成器对象。

def count_up_to(max_num):
    """一个简单的计数生成器"""
    count = 1
    while count <= max_num:
        yield count      # 暂停,返回count,等待下一次唤醒
        count += 1

生成器的基础使用

for循环自动迭代(最推荐)

Python的for循环会隐式地反复调用next(),并在数据耗尽后自动处理StopIteration异常,使用起来最省心:

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

手动调用next()(适合精细控制)

如果需要自己控制取值节奏(例如调试或配合其他逻辑),可以用内置函数next()逐个获取:

gen = count_up_to(3)
print(next(gen))   # 1
print(next(gen))   # 2
print(next(gen))   # 3
# 如果继续调用,会抛出 StopIteration 异常
# next(gen)

生成器的进阶特性

1. 清晰的状态保持过程

在生成器函数中添加一些打印语句,可以直观地看到执行与暂停的切换过程:

def stateful_gen():
    print("🔵 生成器第一次启动")
    yield 1
    print("🟡 从第一个 yield 恢复")
    yield 2
    print("🔴 从第二个 yield 恢复,后面没有值了")

gen = stateful_gen()
# 注意:只有第一次调用 next() 才会真正进入函数!
print(next(gen))   # 输出 🔵 和 1
print(next(gen))   # 输出 🟡 和 2
# 再调用一次会输出 🔴,然后抛出 StopIteration

⚠️ 划重点:生成器必须先调用next()(或通过for循环隐式调用)才能启动执行,不会在创建时自动跑。

2. Python 3.3+ 新增:带返回值的生成器

普通生成器到达终点后只会抛出StopIteration,没有返回值。从Python 3.3开始,你可以在生成器函数里使用return给出一个“最终结果”,这个值会被附加在异常的value属性上,供上层捕获:

def gen_with_final():
    yield "第一步结果"
    yield "第二步结果"
    return "🎉 所有步骤完成!"

gen = gen_with_final()
try:
    while True:
        print(next(gen))
except StopIteration as e:
    print(e.value)   # 输出 🎉

3. Python 3.3+ 新增:yield from 委托子生成器

当你想在一个生成器中遍历另一个可迭代对象并逐个产出时,不必手动写for item in it: yield item,直接用yield from it就能完成“委托”——它会自动把内部迭代器的所有元素一个接一个交出来,同时还负责异常传递等脏活累活:

def chain_generators(*iterables):
    for it in iterables:
        yield from it   # 等价于 for item in it: yield item,但更优雅

gen = chain_generators([1, 2], (3, 4), "ab")
print(list(gen))   # [1, 2, 3, 4, 'a', 'b']

三个高频实际应用

1. 无限斐波那契数列

用列表存储无限序列是不可能的任务,但生成器可以轻松表示“无限长”的逻辑:

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# 只取前10个看看
fib = fibonacci()
for _ in range(10):
    print(next(fib))

2. 逐行读取超大文件

面对几百MB甚至几GB的日志文件,如果用file.readlines()一次性全部读入内存,可能会直接导致内存溢出。改用生成器逐行读取,一次只加载一行,内存友好得多:

def read_large_file(file_path):
    """逐行返回清洗后的行内容,不撑爆内存"""
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            yield line.strip()

# 边读边处理,安全又高效
# for line in read_large_file('huge_server_log.txt'):
#     if 'ERROR' in line:
#         print(line)

3. 数据处理管道(链式组合)

生成器可以和filter()map()这些函数式工具无缝衔接,构建一条懒求值的数据处理流水线——前面步骤不会真正执行,直到最后一步(比如list()for循环)开始消费数据时,整个管道才依次运转:

def get_even_squares(max_num):
    # 第一步:生成所有候选数(生成器表达式,未计算)
    all_nums = (x for x in range(max_num))
    # 第二步:过滤出偶数(也是懒操作)
    even_nums = filter(lambda x: x % 2 == 0, all_nums)
    # 第三步:映射到平方值(依然懒得算)
    even_squares = map(lambda x: x**2, even_nums)
    return even_squares

print(list(get_even_squares(20)))
# 输出 [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

快速补充:生成器的一次性特性

生成器只能迭代一次。一旦数据全部产出完毕,它就“耗尽”了,想再用只能重新创建一个。如果确实需要反复访问数据,可以提前把生成器转成列表,但这样会失去省内存的优势:

gen = (x**2 for x in range(3))
print(list(gen))   # [0, 1, 4]
print(list(gen))   # [] —— 已经没数据了!

小练习:用生成器输出杨辉三角

试着实现一个可以无限输出杨辉三角每一行的生成器。参考答案如下:

def triangles():
    """无限生成杨辉三角的每一行"""
    row = [1]
    while True:
        yield row
        # 下一行:两边是1,中间是相邻两数之和
        row = [1] + [row[i] + row[i+1] for i in range(len(row)-1)] + [1]

# 打印前10行
results = []
for n, t in enumerate(triangles()):
    results.append(t)
    if n == 9:
        break

for row in results:
    print(row)

总结

生成器是Python中非常“Pythonic”的工具,建议在以下场景优先考虑使用

  1. 处理超大数据集或大文件(避免内存爆炸)
  2. 表示无限序列(如斐波那契、无限循环数据流)
  3. 构建懒求值的数据处理管道(提升性能与可读性)
  4. 学习协程基础概念(了解yield的作用,为后续async/await打底)

虽然生成器在纯计算速度上可能略慢于列表推导式,但它在内存占用上的巨大优势,通常远远超过了那一点点速度损失——尤其是在需要处理海量数据的时候,生成器就是你最好的朋友。