- Published on
3.5.生成器
- Authors

- Name
- xiaobai
1.基本概念
生成器是 Python 中一种特殊的迭代器,使用 yield 关键字创建,具有惰性计算特性。
1.1.什么是生成器?
生成器是一种特殊的函数,具有以下特征:
- 惰性计算:按需生成值,不立即计算所有结果
- 内存高效:一次只处理一个值,不存储整个序列
- 可迭代:可以用在 for 循环中
- 状态保持:记住上次执行的位置
- 协程特性:支持双向通信,可以接收外部数据
2.创建生成器的方法
生成器有两种主要创建方式:
- 生成器函数:使用
yield关键字的函数 - 生成器表达式:类似列表推导式的语法,但使用圆括号
2.1.生成器函数 vs 普通函数
| 特性 | 普通函数 | 生成器函数 |
|---|---|---|
| 返回值 | 使用 return | 使用 yield |
| 执行方式 | 一次性执行完毕 | 可以暂停和恢复 |
| 内存使用 | 一次性创建所有结果 | 按需生成,节省内存 |
| 状态保持 | 不保持状态 | 保持执行状态 |
优势:
- 内存效率高,适合处理大数据集
- 支持复杂的控制流
- 可以实现协程模式
- 代码更简洁易读
2.2.方法1:生成器函数
生成器函数使用 yield 关键字创建,每次遇到 yield 会暂停执行并返回值。
语法特点:
- 使用
def关键字定义 - 函数体内必须包含
yield语句 - 调用时返回生成器对象,不立即执行
- 通过
next()或for循环驱动执行
def my_generator():
"""简单的生成器函数示例"""
print("第一步")
yield 'A'
print("第二步")
yield 'B'
print("第三步")
yield 'C'
# 创建生成器对象
gen = my_generator()
# 逐步获取值
print(next(gen)) # 输出: 第一步 \n A
print(next(gen)) # 输出: 第二步 \n B
print(next(gen)) # 输出: 第三步 \n C
# print(next(gen)) # 抛出 StopIteration 异常
# 使用 for 循环遍历
for value in my_generator():
print(f"获取到: {value}")
重要特性:
- 生成器只能遍历一次,用完即耗尽
- 每次
yield会保存函数状态 - 适合处理大量数据或复杂逻辑
- 支持协程模式(双向通信)
2.3.方法2:生成器表达式
生成器表达式是创建生成器最简洁的方式,语法类似列表推导式,但使用圆括号。
基本语法:
(expression for item in iterable [if condition])
示例:
# 创建平方数生成器
gen = (x**2 for x in range(5))
print(type(gen)) # <class 'generator'>
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 4
print(next(gen)) # 9
print(next(gen)) # 16
# print(next(gen)) # StopIteration
# 使用 for 循环遍历
for value in (x*3 for x in range(4)):
print(value, end=" ") # 输出: 0 3 6 9
# 带条件的生成器表达式
even_squares = (x**2 for x in range(10) if x % 2 == 0)
print(list(even_squares)) # [0, 4, 16, 36, 64]
优势:
- 语法简洁,一行代码创建生成器
- 内存效率高,惰性求值
- 适合简单的数据转换和过滤
- 可以与其他函数链式组合
适用场景:
- 简单的数据转换
- 一次性遍历
- 内存敏感的场景
- 与内置函数配合使用
2.4.列表推导式 vs 生成器表达式
numbers = [1, 2, 3, 4, 5]
# 列表推导式 - 立即计算所有结果
squares_list = [x**2 for x in numbers]
print(squares_list) # [1, 4, 9, 16, 25]
print(type(squares_list)) # <class 'list'>
# 生成器表达式 - 惰性计算
squares_gen = (x**2 for x in numbers)
print(squares_gen) # <generator object <genexpr> at 0x...>
print(type(squares_gen)) # <class 'generator'>
# 遍历生成器
for square in squares_gen:
print(square, end=" ") # 1 4 9 16 25
对比总结:
| 特性 | 列表推导式 | 生成器表达式 |
|---|---|---|
| 语法 | [expr for item in iterable] | (expr for item in iterable) |
| 内存使用 | 一次性创建所有元素 | 按需生成,节省内存 |
| 执行时机 | 立即执行 | 惰性求值 |
| 可重复使用 | 是 | 否(只能遍历一次) |
| 适用场景 | 需要多次访问结果 | 一次性遍历,大数据集 |
3.生成器的工作原理
3.1.执行流程
生成器本质上是一个特殊的迭代器,其执行流程与普通函数不同:
- 创建阶段:调用生成器函数时,函数体不会立即执行,而是返回生成器对象
- 驱动阶段:通过
next()或for循环驱动执行 - 暂停恢复:遇到
yield暂停并返回值,下次从暂停处继续 - 结束阶段:函数执行完毕时抛出
StopIteration异常
def example_gen():
print("Step 1")
yield 'A'
print("Step 2")
yield 'B'
print("End")
gen = example_gen()
print(next(gen)) # 输出: Step 1 \n A
print(next(gen)) # 输出: Step 2 \n B
print(next(gen)) # 输出: End \n 抛出 StopIteration
执行步骤:
- 创建生成器:仅返回对象,不执行函数体
- 第一次
next():从函数开始执行,遇到第一个yield暂停 - 后续
next():从上次暂停处恢复,直到函数结束
状态保持:
- 生成器能记住执行位置和局部变量状态
- 每次
yield会保存当前执行状态 - 下次调用时从保存的状态继续执行
3.2.状态保持示例
def fibonacci_generator():
"""生成斐波那契数列的生成器函数"""
a, b = 0, 1 # 初始化前两个斐波那契数
while True: # 无限循环
yield a # 返回当前的a值
a, b = b, a + b # 计算下一个斐波那契数
# 创建生成器
fib = fibonacci_generator()
# 获取前10个斐波那契数
for i in range(10):
print(next(fib), end=" ") # 输出: 0 1 1 2 3 5 8 13 21 34
状态保持特点:
- 变量
a和b的值在每次yield后保持 - 下次调用时从上次暂停的位置继续
- 不需要额外的数据结构来保存状态
- 适合生成无限序列
4.生成器方法
生成器提供了几个特殊方法,支持更高级的交互和控制。
4.1.send() 方法 - 向生成器发送值
send() 方法可以向生成器发送值,实现双向通信。
使用规则:
- 首次启动生成器必须使用
next() - 只有生成器暂停在
yield后才能使用send() send()会返回生成器产生的下一个值
def simple_echo():
"""简单的回声生成器"""
received = yield "请给我一个值"
yield f"你发来的是:{received}"
gen = simple_echo()
print(next(gen)) # 输出: 请给我一个值
print(gen.send("Hello")) # 输出: 你发来的是:Hello
执行流程:
next(gen)启动生成器,执行到第一个yieldgen.send("Hello")将值发送给生成器,继续执行- 生成器接收值并处理,产生下一个结果
应用场景:
- 协程编程
- 状态机实现
- 数据管道处理
- 与外部系统交互
4.2.throw() 方法 - 向生成器抛出异常
throw() 方法可以在生成器暂停处抛出异常,实现异常处理机制。
使用场景:
- 外部主动通知生成器发生错误
- 优雅地中断生成器执行
- 实现异常处理和恢复逻辑
def exception_generator():
"""演示异常处理的生成器"""
try:
yield "开始"
yield "继续"
yield "结束"
except ValueError as e:
yield f"捕获异常: {e}"
yield "恢复执行"
# 使用示例
gen = exception_generator()
print(next(gen)) # 开始
print(next(gen)) # 继续
print(gen.throw(ValueError("测试异常"))) # 捕获异常: 测试异常
print(next(gen)) # 恢复执行
异常处理流程:
- 生成器正常执行到
yield语句 - 外部调用
throw()抛出异常 - 生成器内部捕获异常并处理
- 可以选择恢复执行或终止生成器
注意事项:
- 如果异常未被捕获,会向外传播
- 异常处理完成后,生成器可以继续执行
- 适合实现错误恢复和资源清理
4.3.close() 方法 - 关闭生成器
close() 方法用于主动终止生成器,触发资源清理。
使用场景:
- 读取大文件时提前终止
- 数据库连接管理
- 网络连接清理
- 资源释放
def closable_generator():
"""可关闭的生成器示例"""
try:
yield "第一步"
yield "第二步"
yield "第三步"
except GeneratorExit:
print("生成器被关闭,正在清理资源")
raise # 必须重新抛出异常
# 使用示例
gen = closable_generator()
print(next(gen)) # 第一步
gen.close() # 生成器被关闭,正在清理资源
# 后续调用 next(gen) 会抛出 StopIteration
关闭流程:
- 调用
close()方法 - 生成器在
yield处抛出GeneratorExit异常 - 生成器内部捕获异常并清理资源
- 必须重新抛出异常,否则会触发
RuntimeError
注意事项:
- 只能在生成器暂停时关闭
- 关闭后再次调用
next()会抛出StopIteration - 适合实现资源管理和清理逻辑
5.实际应用场景
5.1.处理大文件
生成器非常适合处理大文件,避免内存溢出:
def read_large_file(file_path):
"""逐行读取大文件,避免内存溢出"""
with open(file_path, 'r', encoding='utf-8') as file:
for line in file:
yield line.strip()
def process_log_file(file_path):
"""处理日志文件,只包含错误日志"""
for line in read_large_file(file_path):
if 'ERROR' in line:
yield line
# 使用示例
for error_line in process_log_file('huge_log_file.log'):
print(error_line)
优势:
- 内存效率高,不会一次性加载整个文件
- 支持流式处理,边读边处理
- 可以随时停止处理
- 适合处理GB级别的文件
5.2.数据管道
生成器可以构建高效的数据处理管道:
def data_processing_pipeline(data):
"""数据处理管道"""
# 步骤1: 过滤有效数据
valid_data = (item for item in data if item is not None)
# 步骤2: 转换数据
transformed_data = (str(item).upper() for item in valid_data)
# 步骤3: 添加序号
numbered_data = (f"{i}: {item}" for i, item in enumerate(transformed_data, 1))
return numbered_data
# 使用管道
raw_data = [None, 'hello', None, 'world', 'python', None]
result = data_processing_pipeline(raw_data)
for item in result:
print(item)
# 输出:
# 1: HELLO
# 2: WORLD
# 3: PYTHON
管道优势:
- 每个步骤都是惰性计算
- 内存效率高,不存储中间结果
- 易于组合和扩展
- 支持流式处理
5.3.无限序列
生成器可以创建无限序列,按需生成数据:
def prime_generator():
"""生成质数的无限序列"""
primes = []
num = 2
while True:
is_prime = True
# 用已找到的质数检查 num
for prime in primes:
if prime * prime > num:
break
if num % prime == 0:
is_prime = False
break
if is_prime:
primes.append(num)
yield num
num += 1
# 获取前20个质数
prime_gen = prime_generator()
first_20_primes = [next(prime_gen) for _ in range(20)]
print(first_20_primes) # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
无限序列优势:
- 按需生成,不预计算所有值
- 内存效率高,只保存必要状态
- 可以随时停止生成
- 适合数学计算和模拟
6.高级生成器模式
6.1.生成器委托 (yield from)
yield from 语句可以简化生成器委托,让代码更简洁。
基本语法:
yield from iterable
对比示例:
# 原始写法
def chain_generators_old(*iterables):
for iterable in iterables:
for item in iterable:
yield item
# 使用 yield from
def chain_generators(*iterables):
for iterable in iterables:
yield from iterable
# 使用示例
gen1 = (x for x in range(3))
gen2 = (x for x in range(3, 6))
gen3 = (x for x in range(6, 9))
chained = chain_generators(gen1, gen2, gen3)
print(list(chained)) # [0, 1, 2, 3, 4, 5, 6, 7, 8]
优势:
- 代码更简洁,无需嵌套循环
- 自动处理子生成器的返回值
- 支持协程通信
- 适合递归遍历树形结构
应用场景:
- 拼接多个生成器
- 递归遍历树形结构
- 协程通信
- 管道式编程
6.2.协程模式
协程是生成器的重要扩展,支持双向通信和协作式编程。
协程特点:
- 使用
yield暂停和恢复执行 - 支持双向数据交换
- 可以接收外部发送的数据
- 适合异步任务调度
def average_coroutine():
"""协程模式:实时计算平均值"""
total = 0
count = 0
average = 0
while True:
value = yield average # 接收外部数据
if value is None:
break
total += value
count += 1
average = total / count
return average
# 使用协程
coro = average_coroutine()
next(coro) # 预激协程
print(coro.send(10)) # 10.0
print(coro.send(20)) # 15.0
print(coro.send(30)) # 20.0
try:
coro.send(None) # 终止协程
except StopIteration as e:
print('最终平均值:', e.value) # 最终平均值: 20.0
协程优势:
- 支持双向通信
- 可以暂停和恢复执行
- 适合异步编程
- 代码结构清晰
应用场景:
- 异步任务调度
- 流式数据处理
- 事件驱动编程
- 状态机实现
7.生成器与迭代器
7.1.关系说明
生成器是 Python 中实现迭代器协议的一种简便方式:
- 生成器本身就是特殊的迭代器,实现了
__iter__()和__next__()方法 - 所有生成器都能用于
for循环,与其他迭代器无缝兼容 - 生成器提供了更简洁的迭代器实现方式
7.2.对比:迭代器类 vs 生成器函数
# 方法1: 自定义迭代器类
class SquareIterator:
def __init__(self, n):
self.n = n
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current >= self.n:
raise StopIteration
result = self.current ** 2
self.current += 1
return result
# 方法2: 生成器函数
def square_generator(n):
for i in range(n):
yield i ** 2
# 使用对比
print("迭代器类:")
for x in SquareIterator(5):
print(x, end=" ") # 0 1 4 9 16
print("\n生成器函数:")
for x in square_generator(5):
print(x, end=" ") # 0 1 4 9 16
对比总结:
| 特性 | 迭代器类 | 生成器函数 |
|---|---|---|
| 代码量 | 较多 | 较少 |
| 状态管理 | 手动管理 | 自动管理 |
| 异常处理 | 手动抛出 StopIteration | 自动处理 |
| 可读性 | 一般 | 更好 |
| 适用场景 | 复杂逻辑 | 简单到中等复杂度 |
推荐使用生成器函数,除非需要复杂的迭代逻辑。
8.性能比较
8.1.内存使用对比
生成器在处理大数据时具有明显的内存优势:
import sys
# 列表推导式 - 一次性创建所有元素
def using_list(n):
result = [i**2 for i in range(n)]
return sum(result)
# 生成器表达式 - 按需生成元素
def using_generator(n):
result = (i**2 for i in range(n))
return sum(result)
n = 1000000
# 内存使用对比
list_result = using_list(n)
gen_result = using_generator(n)
print(f"列表内存使用: {sys.getsizeof(list_result)} 字节")
print(f"生成器内存使用: {sys.getsizeof(gen_result)} 字节")
# 输出示例:
# 列表内存使用: 800984 字节
# 生成器内存使用: 200 字节
8.2.性能优势总结
| 特性 | 列表 | 生成器 |
|---|---|---|
| 内存使用 | 一次性创建所有元素 | 按需生成,节省内存 |
| 执行速度 | 需要预先计算 | 惰性求值,边用边算 |
| 适用场景 | 需要多次访问结果 | 一次性遍历,大数据集 |
| 内存风险 | 可能内存溢出 | 内存安全 |
选择建议:
- 使用列表:需要多次访问结果,数据量不大
- 使用生成器:一次性遍历,大数据集,内存敏感场景
9.实用示例
9.1.分块处理数据
生成器非常适合分块处理大型文件,避免内存溢出:
def chunk_reader(file_path, chunk_size=1024):
"""分块读取文件"""
with open(file_path, 'rb') as file:
while True:
chunk = file.read(chunk_size)
if not chunk:
break
yield chunk
def batch_processor(data, batch_size=100):
"""批量处理数据"""
batch = []
for item in data:
batch.append(item)
if len(batch) >= batch_size:
yield batch
batch = []
if batch: # 处理剩余数据
yield batch
def process_large_dataset(data_generator):
"""处理大型数据集"""
for batch in batch_processor(data_generator, batch_size=50):
print(f"处理批次,大小: {len(batch)}")
processed_batch = [item * 2 for item in batch]
yield from processed_batch
# 使用示例
data_generator = chunk_reader("huge_log_file.log")
for item in process_large_dataset(data_generator):
print(item)
分块处理优势:
- 内存效率高,不会一次性加载整个文件
- 支持流式处理,边读边处理
- 可以处理任意大小的文件
- 易于扩展和组合
9.2.状态机生成器
生成器可以实现状态机,利用协程特性保持状态:
def state_machine():
"""使用生成器实现状态机"""
state = "开始"
while True:
if state == "开始":
command = yield "状态: 开始 - 输入 '运行' 或 '退出'"
if command == "运行":
state = "运行中"
elif command == "退出":
state = "结束"
elif state == "运行中":
command = yield "状态: 运行中 - 输入 '暂停' 或 '停止'"
if command == "暂停":
state = "暂停"
elif command == "停止":
state = "开始"
elif state == "暂停":
command = yield "状态: 暂停 - 输入 '继续' 或 '停止'"
if command == "继续":
state = "运行中"
elif command == "停止":
state = "开始"
elif state == "结束":
return "程序结束"
# 使用状态机
machine = state_machine()
print(next(machine)) # 启动
print(machine.send("运行"))
print(machine.send("暂停"))
print(machine.send("继续"))
print(machine.send("停止"))
状态机优势:
- 利用协程特性保持状态
- 支持交互式控制
- 代码结构清晰
- 易于扩展和维护
应用场景:
- 游戏状态管理
- 工作流控制
- 用户交互系统
- 协议状态机
10.最佳实践
10.1.使用建议
- 内存敏感时使用:处理大数据集时优先考虑生成器
- 一次性使用:生成器通常只能迭代一次
- 合理使用 yield from:简化生成器委托
- 及时关闭:不再使用的生成器应该关闭
- 异常处理:在生成器中适当处理异常
- 文档说明:说明生成器的行为和预期输入
10.2.性能优化
- 优先使用生成器表达式处理简单逻辑
- 使用生成器函数处理复杂逻辑
- 避免在生成器中执行耗时操作
- 合理使用
yield from简化代码
11.常见陷阱
11.1.陷阱1: 生成器只能使用一次
def one_time_use():
gen = (x for x in range(3))
print(list(gen)) # [0, 1, 2]
print(list(gen)) # [] - 空的!
解决方案:每次需要遍历时重新创建生成器
11.2.陷阱2: 生成器表达式语法
# 正确写法
gen1 = (x**2 for x in range(5))
# 函数调用时可以省略外层括号
result = sum(x**2 for x in range(5))
注意:生成器表达式必须用圆括号包围
11.3.陷阱3: 过早耗尽生成器
def premature_exhaustion():
gen = (x for x in range(5))
if 3 in gen: # 这会消耗生成器直到找到3
print("找到3")
# 此时生成器已经耗尽
for item in gen:
print(item) # 不会执行
解决方案:避免在检查后继续使用生成器,或重新创建
11.4.陷阱4: 生成器中的异常处理
def exception_handling():
try:
yield "开始"
yield "继续"
yield "结束"
except Exception as e:
yield f"错误: {e}"
# 注意:异常处理后生成器可能无法继续
建议:在生成器中谨慎处理异常,确保状态一致性

