在Python中,"with"比try/catch打开文件有什么优势?

49 投票
3 回答
40600 浏览
提问于 2025-04-17 09:46

我明白了,with 语句可以帮助你把这个:

try:
    f = open(my_file)
    do_stuff_that_fails()
except:
    pass
finally:
    f.close()

变成这个:

with open(my_file) as f:
    do_stuff_that_fails()

但是这样真的更好吗?你还是得处理文件打不开的情况(比如提示用户他没有权限),所以实际上你会有:

try:
    with open(my_file) as f:
        do_stuff_that_fails()
except (IOError, OSError, Failure) as e:
    do_stuff_when_it_doesnt_work()

这和下面这个是一样的:

try:
    f = open(my_file)
    do_stuff_that_fails()
except (IOError, OSError, Faillure) as e:
    do_stuff_when_it_doesnt_work()
finally:
    f.close()

没错,你省了两行代码,但你增加了一层嵌套,这样反而不容易读。with 语句的目的真的是为了省这两行吗?还是我漏掉了什么?

感觉单单为了这个增加一个关键词有点多,所以我觉得可能还有其他语法可以处理额外的 try/except,我还不知道。

3 个回答

7

这是关于资源管理的内容……而不是你如何处理异常的事情 :)

使用with的时候,你根本不可能“忘记”调用f.close()。这样的话,它的作用和C#里的using是一样的。

祝你编码愉快。

19

在你给的例子中,其实并不是更好。最佳的做法是尽量在抛出异常的地方就捕获它,这样可以避免捕获到同类型但无关的异常。

try:
    file = open(...)
except OpenErrors...:
    # handle open exceptions
else:
    try:
        # do stuff with file
    finally:
        file.close()

虽然这说起来有点啰嗦,但with语句在执行时并不允许你捕获抛出的异常。曾经在邮件列表上有个建议想要给这个功能加上异常处理:

with open(...) as file:
    # do stuff with file
except OpenErrors...:
    # handle open exceptions

但这个建议被否决了

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

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

关于这一点有更详细的描述,可以查看这里这里

一般来说,with语句特别适合那些不太可能出现异常的情况,而且默认的“进入/打开/获取”行为就足够了。比如说,处理一些必要的文件或者简单的锁定操作。

40

首先,这样做可以避免你在 try ... finally ... 示例中引入的问题。

你现在的结构是这样的:如果在尝试打开文件时发生了异常,那么你就永远不会把一个打开的文件绑定到名字 f 上。这会导致在 finally 语句中出现 NameError(如果 f 从来没有被绑定过)或者出现一些完全意想不到的情况(如果它已经被绑定过)。

正确的结构(和 with 是等价的)应该是:

f = open(my_file)

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

(注意 - 如果你没有什么要处理的,except 语句是可以省略的)。

你的第二个示例同样是错误的,应该像这样结构化:

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 一起使用,为特定代码块建立一个十进制上下文。

最后(回应你对之前回答的评论),你绝对不应该依赖引用计数的语义来处理 Python 中的资源。Jython、IronPython 和 PyPy 都有不同于引用计数的语义,而 CPython 也没有什么阻止它朝着相反的方向发展(尽管在短期内不太可能)。在一个紧凑的循环中(例如 os.walk),如果依赖引用计数语义的代码在行为不同的虚拟机上运行,很容易就会耗尽文件句柄。

撰写回答