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”的工具,建议在以下场景优先考虑使用:
- 处理超大数据集或大文件(避免内存爆炸)
- 表示无限序列(如斐波那契、无限循环数据流)
- 构建懒求值的数据处理管道(提升性能与可读性)
- 学习协程基础概念(了解
yield的作用,为后续async/await打底)
虽然生成器在纯计算速度上可能略慢于列表推导式,但它在内存占用上的巨大优势,通常远远超过了那一点点速度损失——尤其是在需要处理海量数据的时候,生成器就是你最好的朋友。