关于异常:如何“with”比try / catch更好地在Python中打开文件?

How “with” is better than try/catch to open a file in Python?

我知道with的声明有助于你扭转这种局面:

1
2
3
4
5
6
7
try:
    f = open(my_file)
    do_stuff_that_fails()
except:
    pass
finally:
    f.close()

进入:

1
2
with open(my_file) as f:
    do_stuff_that_fails()

但这怎么会更好呢?您仍然需要处理无法打开的文件(如提示用户告诉他他没有权限)的情况,因此实际上您可以:

1
2
3
4
5
try:
    with open(my_file) as f:
        do_stuff_that_fails()
except (IOError, OSError, Failure) as e:
    do_stuff_when_it_doesnt_work()

相当于:

1
2
3
4
5
6
7
try:
    f = open(my_file)
    do_stuff_that_fails()
except (IOError, OSError, Faillure) as e:
    do_stuff_when_it_doesnt_work()
finally:
    f.close()

是的,你得到了两行,但是你添加了一个嵌套的wich级别并不能使它更容易阅读。这句话的目的是为了让你省去两行,还是我遗漏了什么?

仅仅为这个添加一个关键字似乎很难,所以我觉得有一些语法可以处理额外的try/except,我不知道。


首先,它有助于防止您在try ... finally ...示例中引入的问题。

您构造它的方式是,如果在试图打开文件时抛出异常,那么您将永远不会将打开的文件绑定到名称为f的文件中,从而导致finally子句中的NameError(如果f从未在范围内绑定)或完全意外的(如果它已经绑定)。

正确的结构(相当于with)是:

1
2
3
4
5
6
f = open(my_file)

try:
    do_stuff_that_fails()
finally:
    f.close()

(注:如果你没有做任何事的话,不需要使用except条款)。

您的第二个示例同样是错误的,其结构应该如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
try:
    f = open(my_file)

    try:
        do_stuff_that_fails()
    except EXPECTED_EXCEPTION_TYPES as e:
        do_stuff_when_it_doesnt_work()
    finally:
        f.close()

except (IOError, OSError) as e:
    do_other_stuff_when_it_we_have_file_IO_problems()

第二个是(如另一个答案所述),你不能忘记打电话给f.close()

顺便说一句,术语是"上下文管理",而不是"资源管理"——with语句管理上下文,其中一些可能是资源,而另一些则不是。例如,它还与decimal一起用于为特定的代码块建立十进制上下文。

最后(对上一个答案的注释作出响应),您不应该依赖refcount语义来处理Python中的资源。Jython、Ironpython和Pypy都有非refcount语义,而且没有什么能阻止CPython向另一个方向发展(尽管这在不久的将来是极不可能的)。如果依赖refcount语义的代码在具有不同行为的VM上运行,则在一个紧密的循环(例如os.walk中)中,很容易耗尽文件句柄。


在你给出的例子中,情况并不好。最好的做法是尽可能接近抛出的点捕获异常,以避免捕获同一类型的无关异常。

1
2
3
4
5
6
7
8
9
try:
    file = open(...)
except OpenErrors...:
    # handle open exceptions
else:
    try:
        # do stuff with file
    finally:
        file.close()

不幸的是,尽管这很冗长,with语句不允许您捕获在其评估期间抛出的异常。有人建议在邮件列表中添加例外处理:

1
2
3
4
with open(...) as file:
    # do stuff with file
except OpenErrors...:
    # handle open exceptions

但这是被击落的。

最后值得注意的是,您可以这样直接进入和退出上下文管理器:

1
2
file = open(...).__enter__()
file.__exit__(typ, val, tb)

这在这里和这里有更详细的描述。

作为一般准则,with语句在不期望出现异常的情况下表现出色,并且默认的"输入/打开/获取"行为是适当的。示例包括所需的文件和简单的锁定。


用于资源管理…不是因为你对异常的反应,否则:)

使用with时,无法"忘记"f.close()。以这种方式,它与c中的using具有相同的作用。

快乐编码。