昨天发布了python迭代器相关内容,在python 中还有一种特殊的迭代器,叫生成器,通过延迟计算(Lazy Evaluation)的方式,按需生成序列中的每个元素,而不是一次性将所有元素加载到内存中。生成器的核心是 yield 关键字,它允许函数在返回值后暂停执行,稍后可以从暂停的位置继续。
1、 设计思路
生成器的设计思路主要围绕以下几个关键点:
- 惰性计算:生成器按需生成数据,避免浪费内存。
- 代码简洁:生成器使用 yield 关键字自动保存函数的状态,不需要手动实现复杂的迭代器协议。
- 无限序列支持:生成器可以用于表示无限序列,避免固定大小的限制。
- 可读性和扩展性:相比传统迭代器,生成器的实现更简洁,代码可维护性更高。
2、生成器的用途和用法
生成器在以下场景中特别有用:
处理大规模数据:如读取大文件、处理海量数据时,生成器可以逐行或逐块读取,避免内存不足。
实现惰性序列:如无限序列生成(斐波那契数列、自然数序列等)。
简化迭代器的实现:相比手动实现迭代器协议,生成器的实现更为简便。
生成器有两种创建方式:
- 使用生成器函数(包含 yield 的普通函数)。
- 使用生成器表达式(与列表推导式类似,但使用圆括号)。
2.1 使用生成器函数
生成器函数是普通的 python 函数,但它包含 yield 关键字。每次调用生成器的 __next__() 方法时,函数会从上次暂停的地方恢复执行。
示例:生成一个有限的数列
def count_up_to(limit):
current = 1
while current <= limit:
yield current # 暂停并返回当前值
current += 1
# 使用生成器
gen = count_up_to(5)
for num in gen:
print(num)
输出:
1
2
3
4
5
2.2 使用生成器表达式
生成器表达式是一种简洁的生成器定义方式,类似于列表推导式,但它使用圆括号而不是方括号。
示例:生成平方数
# 生成器表达式
squares = (x**2 for x in range(1, 6))
# 使用生成器
for square in squares:
print(square)
输出:
1
4
9
16
25
3、 生成器的特点
状态自动保存:每次调用 yield 时,生成器会自动保存当前的执行状态(包括局部变量和程序位置)。下一次调用时,从暂停的地方继续执行。
示例:状态保存
def generator_example():
print("First yield")
yield 1
print("Second yield")
yield 2
gen = generator_example()
print(next(gen)) # 输出 "First yield" 和 1
print(next(gen)) # 输出 "Second yield" 和 2
输出:
First yield
1
Second yield
2
惰性求值(Lazy Evaluation):生成器只在需要时生成值,从而节省内存,特别适用于处理大数据或无限序列。
示例:无限序列
def infinite_numbers():
current = 1
while True:
yield current
current += 1
gen = infinite_numbers()
for _ in range(5):
print(next(gen)) # 按需生成前 5 个数字
输出:
1
2
3
4
5
单次遍历:
生成器只能遍历一次,因为一旦迭代完成,生成器的状态就会被丢弃。如果需要多次遍历,必须重新创建生成器。
示例:单次遍历
def simple_generator():
yield "A"
yield "B"
gen = simple_generator()
print(list(gen)) # ['A', 'B']
print(list(gen)) # 空列表,因为生成器已经耗尽
输出:
['A', 'B']
[]
异常处理:
在生成器中可以使用 try-except 块处理异常,也可以通过外部调用 throw() 方法向生成器注入异常。
示例:生成器中的异常处理
def generator_with_exception():
try:
yield "Start"
yield "Middle"
except ValueError:
yield "Exception handled"
yield "End"
gen = generator_with_exception()
print(next(gen)) # 输出 "Start"
print(gen.throw(ValueError)) # 向生成器注入异常,输出 "Exception handled"
print(next(gen)) # 输出 "End"
输出:
Start
Exception handled
End
支持 send() 方法:生成器可以通过 send() 方法接收外部传入的值并继续执行。
示例:生成器接收外部值
def accumulator():
total = 0
while True:
value = yield total
if value is None:
break
total += value
gen = accumulator()
print(next(gen)) # 初始化生成器,输出 0
print(gen.send(10)) # 输入 10,输出累加结果 10
print(gen.send(20)) # 输入 20,输出累加结果 30
gen.close() # 关闭生成器
输出:
0
10
30
4、 生成器的注意事项
只能前进:生成器是一次性迭代的工具,无法回退或重新开始。如果需要多次访问相同的数据,考虑使用列表或重新创建生成器。
StopIteration 异常:当生成器函数结束时,会自动抛出 StopIteration 异常。这通常由 for 循环或内置函数(如 list())自动处理,用户无需手动捕获。
资源管理:如果生成器涉及外部资源(如文件操作),确保在不需要时关闭生成器(使用 close() 方法或上下文管理器)。
示例:关闭生成器
def file_reader(file_path):
with open(file_path, 'r') as file:
for line in file:
yield line.strip()
gen = file_reader("example.txt")
try:
print(next(gen))
finally:
gen.close()
5、 生成器的高级用法
嵌套生成器 (yield from):
yield from 用于委托另一个生成器。
示例:嵌套生成器
def sub_generator():
yield 1
yield 2
def main_generator():
yield from sub_generator()
yield 3
for value in main_generator():
print(value)
输出:
1
2
3
生成器与协程:
生成器是实现协程的基础,可以通过 send() 方法在执行过程中动态传入数据。
示例:简单协程
def coroutine():
print("Coroutine started")
while True:
value = yield
print(f"Received: {value}")
co = coroutine()
next(co) # 初始化协程
co.send("Hello")
co.send("World")
co.close()
输出:
Coroutine started
Received: Hello
Received: World
总结
生成器是 python 中强大的工具,设计初衷是为了简化迭代器的实现,并提供惰性计算能力。它的主要特点包括状态保存、惰性求值、单次遍历和支持动态值传递。生成器在处理大数据、流式数据以及协程编程中非常有用。通过生成器,我们可以高效、优雅地解决复杂的迭代问题,同时节省内存和提高代码的可读性。