生成器
生成器是 Python 中创建迭代器的简洁而强大的工具。它的写法类似普通函数,但使用 yield 语句返回数据,并在每次产出后暂停执行,保存全部局部状态,下次从暂停处恢复。
yield 语法与生成器函数
在函数体内使用 yield 关键字,该函数就变成了生成器函数。调用生成器函数不会立即执行函数体,而是返回一个生成器对象——一种特殊的迭代器。
def reverse(data):
"""反向产出序列元素的生成器。"""
for index in range(len(data) - 1, -1, -1):
yield data[index]
# 调用生成器函数得到生成器对象
gen = reverse("golf")
print(type(gen)) # <class 'generator'>
# 用 for 循环消费
for char in gen:
print(char) # f, l, o, g
生成器对象自动实现了 __iter__() 和 __next__() 方法,因此它既是可迭代对象也是迭代器。当 __next__() 被调用时,生成器函数从上次离开的位置恢复执行,直到遇到下一个 yield 或函数结束。
def simple_gen():
print("开始")
yield 1
print("恢复1")
yield 2
print("恢复2")
yield 3
print("结束")
g = simple_gen()
print(next(g)) # 开始 → 1
print(next(g)) # 恢复1 → 2
print(next(g)) # 恢复2 → 3
print(next(g)) # 结束 → StopIteration
yield 右侧的表达式值会被返回给调用者,而生成器函数的局部变量、执行位置、指令指针等全部状态都被冻结保存。这种自动状态管理让生成器比基于类的迭代器更易编写、更易读。
生成器与迭代器的关系
生成器是迭代器的一种实现方式,但比普通迭代器更强大。二者的关系可以概括为:
- 所有生成器都是迭代器(继承自
Iterator)。 - 生成器自动实现
__iter__()和__next__(),无需手动编写。 - 生成器自动处理
StopIteration:函数正常结束时,生成器自动抛出StopIteration。 - 生成器支持额外的方法:
send()、throw()、close()。
from collections.abc import Iterator
def gen():
yield 1
g = gen()
print(isinstance(g, Iterator)) # True
print(iter(g) is g) # True
send():双向通信
send(value) 方法允许调用者向生成器发送数据。生成器内部的 yield 表达式可以接收这个值,从而实现双向数据流。
def running_average():
"""实时计算移动平均值的生成器。"""
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total / count
avg = running_average()
print(next(avg)) # 必须先调用 next() 或 send(None) 启动生成器
print(avg.send(10)) # 10.0
print(avg.send(20)) # 15.0
print(avg.send(30)) # 20.0
生成器启动后停在第一个 yield average 处。avg.send(10) 将 10 赋给 term,然后继续执行直到下一个 yield。注意:首次启动生成器时只能使用 next(avg) 或 avg.send(None),因为此时没有 yield 表达式在等待接收值。
def echo():
"""回显生成器:接收什么就返回什么。"""
while True:
received = yield "等待输入..."
yield f"收到:{received}"
g = echo()
print(next(g)) # 启动,输出 "等待输入..."
print(g.send("hello")) # 输出 "收到:hello"
print(next(g)) # 再次到第一个 yield,输出 "等待输入..."
print(g.send("world")) # 输出 "收到:world"
throw():向生成器注入异常
throw(type[, value[, traceback]]) 在生成器当前暂停的位置抛出指定异常。生成器可以在内部捕获该异常,或者让它传播出去。
def controlled_gen():
try:
yield "正常状态"
except ValueError:
yield "已处理 ValueError"
yield "继续执行"
g = controlled_gen()
print(next(g)) # 正常状态
print(g.throw(ValueError)) # 已处理 ValueError
print(next(g)) # 继续执行
如果生成器没有捕获抛入的异常,异常会向上传播到调用者。这在需要优雅终止生成器或通知它外部状态变化时很有用。
def infinite_counter():
n = 0
while True:
try:
yield n
n += 1
except ZeroDivisionError:
print("计数器被重置")
n = 0
g = infinite_counter()
print(next(g)) # 0
print(next(g)) # 1
print(next(g)) # 2
g.throw(ZeroDivisionError) # 计数器被重置
print(next(g)) # 0
close():终止生成器
close() 方法在生成器暂停处抛出 GeneratorExit 异常。如果生成器捕获了该异常并正常退出,close() 静默完成;如果生成器忽略该异常或产生新值,会触发 RuntimeError。
def resource_manager():
print("资源初始化")
try:
yield "资源使用中"
finally:
print("资源清理")
g = resource_manager()
print(next(g)) # 资源初始化 → 资源使用中
g.close() # 触发 GeneratorExit → 执行 finally → 资源清理
close() 的典型用途是释放生成器持有的外部资源。当生成器被垃圾回收时,Python 会自动调用其 close() 方法,因此通常不需要手动调用——但在需要立即释放资源时,显式调用更可靠。
def file_reader(path):
f = open(path, "r")
try:
for line in f:
yield line.strip()
finally:
f.close()
print("文件已关闭")
# 只读取前两行就终止
g = file_reader("data.txt")
print(next(g))
print(next(g))
del g # 垃圾回收时会自动调用 close()
生成器的状态生命周期
生成器对象有三种状态:
- GEN_CREATED:已创建,尚未启动。此时只能调用
next()或send(None)。 - GEN_SUSPENDED:已启动,停在某个
yield处。可以调用next()、send()、throw()、close()。 - GEN_CLOSED:已关闭(正常结束、抛出未捕获异常、或
close()被调用)。此时任何操作都会触发StopIteration或RuntimeError。
import inspect
def demo():
yield 1
yield 2
g = demo()
print(inspect.getgeneratorstate(g)) # GEN_CREATED
next(g)
print(inspect.getgeneratorstate(g)) # GEN_SUSPENDED
next(g)
print(inspect.getgeneratorstate(g)) # GEN_SUSPENDED(停在第二个 yield)
try:
next(g)
except StopIteration:
pass
print(inspect.getgeneratorstate(g)) # GEN_CLOSED
生成器替代类迭代器
任何能用生成器完成的功能,同样可以用基于类的迭代器完成。但生成器的写法更紧凑,因为它自动创建 __iter__() 和 __next__(),自动保存局部变量状态,自动引发 StopIteration。
# 基于类的迭代器(繁琐)
class RangeIterator:
def __init__(self, start, stop):
self.current = start
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.current >= self.stop:
raise StopIteration
val = self.current
self.current += 1
return val
# 生成器(简洁)
def range_gen(start, stop):
while start < stop:
yield start
start += 1
# 两者行为完全一致
print(list(RangeIterator(0, 3))) # [0, 1, 2]
print(list(range_gen(0, 3))) # [0, 1, 2]