未使用的生成器中的上下文管理器如何最终确定?

时间:2019-01-17 16:48:30

标签: python generator contextmanager

我不了解如何以及何时关闭未完成的生成器中的上下文管理器。考虑以下上下文管理器和功能:

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之后发生。但是,我想更好地了解这里发生的事情以及谁触发了事情。

1 个答案:

答案 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