我有一个包含两个嵌套的with
个块的Python应用程序,其中一个块发生在对象的__iter__
方法中.我观察到,将可迭代对象包装在生成器表达式中会改变引发异常时完成这with
个块的顺序,我对此感到非常惊讶.
我使用的是python3.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
似乎将Container
包装在生成器表达式中会导致在异常处理完成后清理其__iter__
方法中的with
块,尽管它嵌套在引发异常时被清理的另一个with
块中.
为什么将Iterable包装在生成器表达式中会改变其__iter__
方法内的with
块在异常处理期间被终结的方式?
编辑:
感谢@user2357112的回答,我看到省略绑定到生成器表达式的局部变量可以得到我所期望的上下文处理:
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
这种行为真的让我感到惊讶,因为我预计命名生成器表达式container_gen
中的活动with
块会像my_code
中的活动with
块一样被清理,而不是它会挂起,只有在本地名称container_gen
超出范围时才会被清理.