为什么我的上下文管理器在异常发生时没有退出

huangapple go评论60阅读模式
英文:

Why does my context manager not exit on exception

问题

以下是您要翻译的部分:

Original Implementation:
原始实现:

@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()

I expect this code to print:
我期望这段代码输出:

open
<line 1 of a.txt>
closed
ValueError: I/O operation on closed file.

But instead it prints:
但实际上它输出:

open
<line 1 of a.txt>
<line 2 of a.txt>

It didn't close the file!
它没有关闭文件!

This seems to contradict python's docs which state that __exit__ will be called whether the with statement exited successfully or with an exception:
这似乎与Python的文档相矛盾,文档中指出__exit__将被调用,无论with语句是成功退出还是出现异常退出。

object.exit(self, exc_type, exc_value, traceback)

Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.

Interestingly, when I reimplemented the context manager as shown below, it worked as expected:
有趣的是,当我按照下面所示重新实现上下文管理器时,它按预期工作:

class open_read(ContextDecorator):
    def __init__(self, path: str):
        self.path = path
        self.f = None

    def __enter__(self):
        self.f = open(self.path, 'r')
        print('open')
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()
        print('closed')

Why didn't my original implementation work?
为什么我的原始实现不起作用?

英文:

I am learning about context managers and was trying to build one myself. The following is a dummy context manager that opens a file in read mode (I know I can just do with open(...): .... this is just an example I built to help me understand how to make my own context managers):

@contextmanager
def open_read(path: str):
    f = open(path, &#39;r&#39;)
    print(&#39;open&#39;)
    yield f
    f.close()
    print(&#39;closed&#39;)


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


foo()

I expect this code to print:

open
&lt;line 1 of a.txt&gt;
closed
ValueError: I/O operation on closed file.

But instead it prints:

open
&lt;line 1 of a.txt&gt;
&lt;line 2 of a.txt&gt;

It didn't close the file!

This seems to contradict python's docs which state that __exit__ will be called whether the with statement exited successfully or with an exception:

> object.exit(self, exc_type, exc_value, traceback)
>
> Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.

Interestingly, when I reimplemented the context manager as shown below, it worked as expected:

class open_read(ContextDecorator):
    def __init__(self, path: str):
        self.path = path
        self.f = None

    def __enter__(self):
        self.f = open(self.path, &#39;r&#39;)
        print(&#39;open&#39;)
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()
        print(&#39;closed&#39;)

Why didn't my original implementation work?

答案1

得分: 5

The line f.close() is never reached (we exit that frame early due to unhandled exception), and then the exception was "handled" in the outer frame (i.e. within foo).

If you want it to close regardless, you'll have to implement it like that:

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

However, I'd like to point out that the built-in open is already returning a context-manager, and you may be reinventing stdlib contextlib.closing.

英文:

The line f.close() is never reached (we exit that frame early due to unhandled exception), and then the exception was "handled" in the outer frame (i.e. within foo).

If you want it to close regardless, you'll have to implement it like that:

@contextmanager
def open_read(path: str):
    f = open(path, &#39;r&#39;)
    try:
        print(&#39;open&#39;)
        yield f
    finally:
        f.close()
        print(&#39;closed&#39;)

However, I'd like to point out that that the built-in open is already returning a context-manager, and you may be reinventing stdlib contextlib.closing.

huangapple
  • 本文由 发表于 2023年2月10日 06:11:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/75404979.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定