You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何这段生成器代码中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生成器的惰性执行+暂停特性搞的鬼,咱们一步步捋执行流程就懂了:

  1. 生成器启动前的状态:当你写gen = tricky_generator(5)时,生成器根本没开始执行,只是创建了一个“待唤醒”的对象,啥代码都没跑。
  2. 列表推导式的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的位置不动了。
  3. 结果打印与生成器回收:当列表推导式拿到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 17:42:58