我不了解如何以及何时关闭未完成的生成器中的上下文管理器。考虑以下上下文管理器和功能:
from contextlib import contextmanager
@contextmanager
def ctx():
print('enter ctx')
yield
print('exit ctx')
def gen_nums(n):
with ctx():
yield from range(n)
我的第一个直觉是,如果我调用gen_nums
但不完全使用生成器,ctx
将永远不会关闭,这很令人担忧。例如:
for i, j in zip(range(5), gen_nums(10)):
print(f'{i}, {j}')
此处{<1>}不是打印在这里。如我所见,这意味着如果生成器中有文件上下文,它将保持打开状态。但是我随后意识到对文件执行相同操作实际上可以正确关闭文件。经过一些测试,我发现如果这样做:
exit ctx
现在from contextlib import contextmanager
@contextmanager
def ctx():
print('enter ctx')
try:
yield
finally:
print('exit ctx')
被打印在末尾。因此,我想某些异常会在某个时刻触发,但我不知道在哪里,何时何地发生(我尝试使用exit ctx
打印该异常,但它不起作用)。似乎在删除生成器时会发生这种情况,因为如果这样做:
except BaseException as e
然后g = gen_nums(10)
for i, j in zip(range(5), g):
print(f'{i}, {j}')
del g
仅在exit ctx
之后发生。但是,我想更好地了解这里发生的事情以及谁触发了事情。
答案 0 :(得分:0)
考虑一下:
@contextmanager
def ctx():
print('enter ctx')
try:
print('ctx begins yield')
yield
print('ctx finishes yield')
finally:
print('exit ctx')
def gen_nums(n):
print('entering generator')
l = range(n)
with ctx():
print('inside context manager')
for i in l:
print('gen before yield')
yield i
print('gen after yield')
print('existing ctx')
print('exiting generator')
结果是:
>>> g = gen_nums(3)
>>> next(g)
entering generator
enter ctx
ctx begins yield
inside context manager
gen before yield
0
>>> next(g)
gen after yield
gen before yield
1
>>> next(g)
gen after yield
gen before yield
2
>>> next(g)
gen after yield
exiting ctx
ctx finishes yield
exit ctx
exiting generator
Traceback (most recent call last):
File "<pyshell#165>", line 1, in <module>
next(g)
StopIteration
似乎contextmanager
才真正在generator
的最后一次迭代之后且到达StopIteration
之前的点退出。因此,在generator
的最后一次迭代中,如果contextmanager
没有在for循环中运行(它处理StopIteration
),它将保持打开状态。
您将无法捕获StopIteration
中的contextmanager
,因为它发生在generator
用完之后 {1}}将已经退出。
此外,IIRC在构造contextmanager
时也应始终使用contextmanager
。