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

生成器表达式包裹可迭代对象为何改变上下文清理顺序?

生成器表达式为何改变嵌套with块的异常收尾顺序?

我开发的Python应用包含两个嵌套with块,其中一个位于对象的__iter__方法内。发现抛出异常时,用生成器表达式包裹可迭代对象会改变这些with块的收尾顺序,Python 3.11.5和3.8.17均存在此行为。

复现代码

from contextlib import closing, contextmanager


class Reader:
    def close(self):
        print(f"CLOSE: {self.__class__.__name__}")

    @contextmanager
    def get_resource(self):
        try:
            yield 42
        finally:
            print(f"CLOSE: {self.__class__.__name__} releases resource")


class Container:
    def __init__(self, reader: Reader):
        self._reader = reader

    def __iter__(self):
        with self._reader.get_resource() as resource:
            for x in range(10):
                yield x


def my_code():
    with closing(Reader()) as reader:
        container = Container(reader)
        container_gen = (x for x in container)
        for thing in container_gen:
            assert False


def expected():
    with closing(Reader()) as reader:
        container = Container(reader)
        for thing in container:
            assert False


print(">>> What my code does:")
try:
    my_code()
except AssertionError:
    print("handle exception")

print()

print(">>> The context handling I expected:")
try:
    expected()
except AssertionError:
    print("handle exception")

运行输出

>>> What my code does:
CLOSE: Reader
handle exception
CLOSE: Reader releases resource

>>> The context handling I expected:
CLOSE: Reader releases resource
CLOSE: Reader
handle exception

补充发现

不将生成器表达式绑定到局部变量时,上下文处理行为符合预期:

def my_code_without_local():
    with closing(Reader()) as reader:
        container = Container(reader)
        for thing in (x for x in container):
            assert False

输出:

CLOSE: Reader releases resource
CLOSE: Reader
handle exception

原因解析

核心在于生成器对象的生命周期和引用计数:

  • 在my_code中,生成器表达式被赋值给了局部变量container_gen。当assert False抛出异常时:

    1. 内层for循环终止,但container_gen仍持有生成器对象的引用,导致Container.__iter__里的生成器不会被立即销毁。
    2. 外层closing的with块先退出,执行reader.close(),打印CLOSE: Reader。
    3. 异常被外层try-except捕获,打印handle exception。
    4. 直到my_code函数执行完毕,局部变量container_gen被销毁,__iter__里的生成器才会被垃圾回收,此时才触发with块的finally逻辑,打印资源释放信息。
  • 在expected函数中,直接迭代container:
    异常会直接传播到__iter__的with块,触发finally完成资源释放,之后才会退出外层closing的with块,顺序符合嵌套上下文的预期。

  • 在my_code_without_local中,生成器表达式没有被绑定到变量:
    for循环抛出异常后,生成器对象失去所有引用,会被立即垃圾回收,因此__iter__里的with块先完成清理,再执行外层closing的逻辑。

内容的提问来源于stack exchange,提问作者Adam Tuft

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 02:46:22