从内部使用语句和__exit__方法生成上下文管理器

时间:2017-05-16 15:36:45

标签: python generator contextmanager

请考虑使用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语句(它在yield语句之后“停止”并且从未继续)。当发电机的参考计数降至0时,是否可以保证将调用__exit__

2 个答案:

答案 0 :(得分:4)

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

这是在Python 2.5中引入的,in the same PEP as send and yield expressions。在此之前,你不能try finally withyield,如果DBI:mysql:database=$database;host=$hostname;port=$port 语句已存在于2.5之前,你就不会有能够host里面的任何一个。

答案 1 :(得分:1)

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

file类似乎无声地传递GeneratorExit异常,因为没有任何信号表明它。但是,如果您在argc方法中打印myfile.__exit__,则会看到上下文未自然关闭:

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