生成器是协程调度的轻量载体,其对象封装代码对象、栈帧、指令偏移及执行上下文;yield 暂停保留局部变量,gi_frame.f_lasti 记录下条字节码;不可重入,StopIteration 后再调 next() 报 RuntimeError。

red">Python 生成器不是语法糖,是协程调度的轻量载体;不理解 yield 的状态机本质,就只能靠试错调 StopIteration。
生成器对象到底在内存里存了什么
调用含 yield 的函数时,Python 不执行函数体,而是立即返回一个 generator 对象。这个对象内部封装了:代码对象(__code__)、局部变量栈帧(gi_frame)、当前指令偏移(gi_running 和 gi_suspended 标志)、以及一个可恢复的执行上下文。
关键点:
-
gi_frame.f_lasti记录下一条要执行的字节码位置,每次next()就从这里继续 - 局部变量不会被销毁——
yield暂停时,所有变量仍驻留在gi_frame.f_locals中 - 生成器对象不可重入:一旦抛出
StopIteration,gi_frame被设为None,再调next()必报RuntimeError: generator already exhausted
yield 和 yield from 的行为差异
yield 返回单个值并暂停;yield from 是委托协议,把子生成器的产出、异常传递和关闭信号全部代理出去。
立即学习“Python免费学习笔记(深入)”;
常见误用场景:
- 想用
yield from range(100000)替代for i in range(100000): yield i?可以,但注意:yield from在 CPython 中有额外的帧切换开销,小数据量反而略慢 - 子生成器抛异常时,
yield from会原样向上冒泡;而手动for x in subgen(): yield x会吞掉子生成器的GeneratorExit -
yield from内部调用了subgen().__iter__(),所以能委托任意可迭代对象(包括列表、文件对象),不只是生成器
生成器退出时的资源清理陷阱
生成器函数中用 try...finally 可以保证退出时执行清理逻辑,但必须注意触发时机:
def safe_reader(filename):
f = open(filename)
try:
for line in f:
yield line.strip()
finally:
print("file closed")
f.close()
正常耗尽
gen = safe_reader("data.txt")
list(gen) # → 打印 "file closed"
提前中断
gen = safe_reader("data.txt")
next(gen)
gen.close() # → 打印 "file closed"
但这样不行:
gen = safe_reader("data.txt")
next(gen)
del gen # 不保证立刻触发 finally!依赖 GC,可能延迟甚至不触发
真正可靠的释放方式只有显式调用 close() 或用 with + 自定义上下文管理器包装生成器。
用生成器实现有限状态机(FSM)的最小可行模式
生成器天然适合建模状态流转:每个 yield 是一个稳定状态,send() 是外部输入事件。
def traffic_light():
state = "red"
while True:
cmd = yield state
if state == "red" and cmd == "go":
state = "green"
elif state == "green" and cmd == "stop":
state = "yellow"
elif state == "yellow" and cmd == "stop":
state = "red"
light = traffic_light()
next(light) # 启动,输出 "red"
print(light.send("go")) # → "green"
print(light.send("stop")) # → "yellow"
print(light.send("stop")) # → "red"
注意:send() 不能发给刚创建未启动生成器(需先 next() 或 send(None)),否则报 TypeError: can't send non-None value to a just-started generator。
这种写法容易忽略的是错误处理——没匹配的 cmd 会导致无限循环在当前状态,建议加默认分支抛异常或返回状态码。










