在Python中,"with"比try/catch打开文件有什么优势?
我明白了,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 个回答
这是关于资源管理的内容……而不是你如何处理异常的事情 :)
使用with
的时候,你根本不可能“忘记”调用f.close()
。这样的话,它的作用和C#里的using
是一样的。
祝你编码愉快。
在你给的例子中,其实并不是更好。最佳的做法是尽量在抛出异常的地方就捕获它,这样可以避免捕获到同类型但无关的异常。
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
语句特别适合那些不太可能出现异常的情况,而且默认的“进入/打开/获取”行为就足够了。比如说,处理一些必要的文件或者简单的锁定操作。
首先,这样做可以避免你在 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
),如果依赖引用计数语义的代码在行为不同的虚拟机上运行,很容易就会耗尽文件句柄。