为何这段生成器代码中finally块会在结果打印后执行?
为何这段生成器代码中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}") gen = tricky_generator(5) result = [next(gen) for _ in range(5)] print("Result:", result)
问题核心
运行后会看到诡异的现象:所有finally块里,唯独i=4的那句在Result: [0, -1, 1, -1, 2]打印之后才输出,这和我们对try/finally的常规认知(finally一定在try/except块结束后立刻执行)不符。
原因拆解
其实这完全是Python生成器的惰性执行+暂停特性搞的鬼,咱们一步步捋执行流程就懂了:
- 生成器启动前的状态:当你写
gen = tricky_generator(5)时,生成器根本没开始执行,只是创建了一个“待唤醒”的对象,啥代码都没跑。 - 列表推导式的5次唤醒:
[next(gen) for _ in range(5)]会循环5次调用next(gen),每一次调用都是给生成器“拍一下屁股让它接着跑”:- 第1次
next:i=0,进入try块,满足偶数条件,执行yield 0——生成器直接暂停在这儿了! 注意,此时try块还没走完,finally块完全没碰,先把0返回给列表。 - 第2次
next:生成器从上次暂停的地方续上,先把i=0的finally块执行完(打印那句i=0的日志),然后进入i=1的循环。i=1是奇数,抛异常被捕获,打印错误后yield -1,又暂停,i=1的finally还没执行。 - 第3次
next:先跑i=1的finally,然后i=2,yield 1,暂停,i=2的finally没动。 - 第4次
next:跑i=2的finally,i=3抛异常,yield -1,暂停,i=3的finally没跑。 - 第5次
next:跑i=3的finally,i=4是偶数,yield 2,再次暂停! 重点中的重点:这时候i=4的try块还没执行完毕,finally块完全没被触发,生成器就停在yield的位置不动了。
- 第1次
- 结果打印与生成器回收:当列表推导式拿到5个值拼成
result后,立刻执行print("Result:", result)输出结果。这时候生成器gen已经没人再去调用next了,Python的垃圾回收机制会在空闲时把这个“没人管”的生成器对象回收掉。而回收的过程中,Python会让生成器从上次暂停的位置(i=4的yield之后)把剩下的代码跑完——也就是执行i=4的finally块,所以你就看到这句日志在Result之后才出来。
解决办法(可选)
如果想让所有finally块都在Result打印前执行,有几种简单方式:
- 把列表推导式换成
for循环,让生成器被彻底迭代完:result = list(tricky_generator(5)) - 在列表推导式之后显式调用
gen.close(),手动触发生成器的收尾流程 - 多调用一次
next(gen)(不过会触发StopIteration异常,需要用try/except捕获)
备注:内容来源于stack exchange,提问作者Puna
相关产品推荐
相关产品推荐

