为什么我的上下文管理器不会因异常而退出

Eya*_*utz 6 python contextmanager python-decorators

我正在学习上下文管理器,并试图自己构建一个。以下是一个虚拟上下文管理器,它以读取模式打开一个文件(我知道我可以这样做with open(...): ...。这只是我构建的一个示例,旨在帮助我了解如何创建自己的上下文管理器):

@contextmanager
def open_read(path: str):
    f = open(path, 'r')
    print('open')
    yield f
    f.close()
    print('closed')


def foo():
    try:
        with open_read('main.py') as f:
            print(f.readline())
            raise Exception('oopsie')
    except Exception:
        pass
    print(f.readline())


foo()

Run Code Online (Sandbox Code Playgroud)

我希望这段代码能够打印:

open
<line 1 of a.txt>
closed
ValueError: I/O operation on closed file.
Run Code Online (Sandbox Code Playgroud)

但它打印的是:

open
<line 1 of a.txt>
<line 2 of a.txt>
Run Code Online (Sandbox Code Playgroud)

它没有关闭文件!

这似乎与 python 的文档相矛盾,该文档声明无论语句成功退出还是出现异常__exit__都会调用该函数:with

目的。退出(自身、exc_type、exc_value、回溯)

退出与该对象相关的运行时上下文。参数描述导致上下文退出的异常。如果上下文退出时没有异常,则所有三个参数都将为 None。

有趣的是,当我重新实现上下文管理器(如下所示)时,它按预期工作:

open
<line 1 of a.txt>
closed
ValueError: I/O operation on closed file.
Run Code Online (Sandbox Code Playgroud)

为什么我原来的实现没有成功?

wim*_*wim 5

该行f.close()永远不会到达(由于未处理的异常,我们提前退出该帧),然后异常在外帧(即在foo)中“处理”。

如果您希望它无论如何关闭,您必须像这样实现它:

@contextmanager
def open_read(path: str):
    f = open(path, 'r')
    try:
        print('open')
        yield f
    finally:
        f.close()
        print('closed')
Run Code Online (Sandbox Code Playgroud)

但是,我想指出,内置函数open已经返回了上下文管理器,并且您可能正在重新发明 stdlib contextlib.closing