
生成器函数中,finally 块并非总在 yield 返回值前立即执行;当迭代未完成(如生成器对象被垃圾回收)时,解释器会自动触发剩余 finally 逻辑,导致其出现在主程序输出之后。
生成器函数中,`finally` 块并非总在 `yield` 返回值前立即执行;当迭代未完成(如生成器对象被垃圾回收)时,解释器会自动触发剩余 `finally` 逻辑,导致其出现在主程序输出之后。
在 Python 中,生成器函数的执行具有暂停-恢复-清理三阶段特性。try...except...finally 结构中的 finally 块保证“无论是否异常、是否提前退出,只要对应 try 块已进入,finally 就一定会执行”。但关键在于:它何时执行?
在你的代码中:
def tricky_generator(n):
for i in range(n):
try:
if i % 2 == 0:
yield i // 2 # ← 暂停点:函数在此挂起,控制权交还调用方
else:
raise ValueError(f"Odd value: {i}")
except ValueError as e:
print(e)
yield -1
finally:
print(f"Finally block executed for i={i}") # ← 注意:此行不总在 yield 后“立刻”执行!
你通过 [next(gen) for _ in range(5)] 显式拉取了 5 个值,对应 i = 0,1,2,3,4 的完整循环。但注意:
- 当
i = 4时(偶数),执行yield 4 // 2 → yield 2,函数在此暂停,等待下一次next(); - 此时
finally尚未执行(因为try块虽已进入,但函数被yield中断,尚未自然退出该try作用域); - 由于你只调用了 5 次
next(),循环已结束,但生成器对象gen仍存活于内存中,且处于i=4的yield暂停状态; - 当脚本执行完
print("Result:", result)后,局部变量gen失去引用,触发垃圾回收 → Python 会强制关闭生成器(即调用其close()方法); - 关闭过程会向生成器注入
GeneratorExit异常,使其从暂停点继续执行至函数末尾 —— 此时才真正“退出”i=4对应的try块,从而触发其finally。
这就是为什么你会看到:
Finally block executed for i=4 Result: [0, -1, 1, -1, 2]
(实际输出顺序可能因缓冲或解释器实现略有差异,但 i=4 的 finally 确实在 Result: 之后打印)
✅ 正确做法:显式耗尽或关闭生成器,避免依赖 GC 触发清理:
gen = tricky_generator(5)
result = []
try:
for _ in range(5):
result.append(next(gen))
finally:
gen.close() # 主动关闭,确保 finally 立即执行
print("Result:", result)
⚠️ 注意事项:
- 不要依赖垃圾回收时机来执行关键清理逻辑(如释放文件句柄、数据库连接);
- 若
finally中有副作用(如日志、状态更新),应确保生成器被显式关闭; -
yield不等于return:它不结束try块,只是暂停;finally的触发取决于try块是否“完成退出”,而非yield是否发生。
总结:生成器中 finally 的执行时机由控制流退出 try 块的时刻决定,而 yield 仅暂停不退出——未完成的生成器在被销毁时补全清理,这正是 i=4 的 finally 晚于 Result 输出的根本原因。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











