Python生成器(Generator)教程

什么是生成器

生成器(Generator)是Python中特殊的懒加载迭代器——它不会一次性把所有值生成并塞到内存里,而是等你要下一个值的时候,才“临时干活”算出来。这种特性在处理超大数据集、无限长度序列时,能直接把内存占用从“爆炸”压到“友好”级别。

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

  • 极致省内存:只会保存当前计算上下文,而不是所有历史/未来值
  • 状态保持强:执行到yield会自动暂停,下次调用next()for循环会从断点继续
  • 代码超简洁:比手写迭代器类(要实现__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关键字,它就自动变成生成器函数:

def count_up_to(max_num):
    count = 1
    # 每次循环执行到yield就暂停,返回count,然后等下一次调用
    while count <= max_num:
        yield 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
# print(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
# next(gen)  # 会输出🔴然后抛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  # 直接把每个可迭代对象的元素“转交”出去

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. 逐行读取超大文件

如果是GB甚至TB级的日志文件,用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()  # strip()去掉换行和空白符

# 逐行处理,内存只占用一行的大小
# for line in read_large_file('huge_server_log.txt'):
#     if 'ERROR' in line:
#         print(line)

3. 数据处理管道(链式调用)

生成器可以和filter()map()这些函数式工具结合,形成“只在最后输出时才执行所有步骤”的管道:

def get_even_squares(max_num):
    # 第一步:生成奇数?不对,后面要filter,其实生成所有数也行
    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))  # 空列表!因为已经迭代完了

小练习:杨辉三角生成器

试试用生成器实现无限输出杨辉三角的前n行(答案在原博客但可以自己写一下哦):

def triangles():
    row = [1]
    while True:
        yield row
        # 下一行 = 开头1 + 中间相邻两个数的和 + 结尾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. 简单的协程编程(虽然现在Python 3.5+有async/await专门做异步,但生成器协程的基础还是要了解的)

虽然生成器的速度比列表推导式慢一点点,但在内存面前,这点速度损失通常是值得的!