从上下文管理器的 with 语句和 __exit__ 方法中产生

Yielding from within with statement and __exit__ method of context manager

考虑以下 Python 2.x 代码段。

from __future__ import print_function


class myfile(file):
    def __exit__(self, *excinfo):
        print("__exit__ called")
        super(myfile, self).__exit__(*excinfo)


def my_generator(file_name):
    with myfile(file_name) as fh:
        for line in fh:
            yield line.strip()


gen = my_generator('file.txt')
print(next(gen))
print("Before del")
del gen
print("After del")

此脚本的输出(给定 file.txt 有多行)是:

Line 1 from file
Before del
__exit__ called
After del

我对 __exit__ 电话特别感兴趣。

是什么触发了他的方法的执行?据我们所知,代码从未离开 with 语句(它 "stopped" 在 yield 语句之后并且从未继续)。生成器的引用计数降为0时,是否保证__exit__会被调用?

要添加到 with 块会在其中引发异常时中断。此异常被传递给为上下文创建的对象的 __exit__ 方法。

file class 似乎默默地传递了 GeneratorExit 异常,因为没有任何信号表明它。然而,如果你在你的 myfile.__exit__ 方法中打印 argc ,你会看到上下文没有自然关闭:

class myfile(file):
    def __exit__(self, *excinfo):
        print("__exit__ called")
        print(excinfo[0]) # Print the reason why the context exited
        super(myfile, self).__exit__(*excinfo)

脚本输出:

Line 1 from file
Before del
__exit__ called
<type 'exceptions.GeneratorExit'>
After del

在回收生成器对象时,Python 调用其 close 方法,如果不是,则在最后一个 yield 点引发 GeneratorExit 异常已经执行完毕。当此 GeneratorExit 传播时,它会触发您使用的上下文管理器的 __exit__ 方法。

这是在 Python 2.5、in the same PEP as send and yield expressions 中引入的。在那之前,你不能在 tryfinallyyield,如果 with 语句在 2.5 之前就已经存在,你将无法 yield里面一个也行。