理解 Python 的 with 语句
翻译自 Understanding Python’s “with” statement,有改动。
从 comp.lang.python 以及其它论坛来看,Python 2.5 新引入的 with 语句对于有经验的 Python 编程人员来说,甚至都有点困惑。
其实正如 Python 中的其他事物一样,一旦你理解了 with 语句尝试解决的问题,就会发现其实它非常简单。考虑下面这段代码:
set things up
try:
do something
finally:
tear things down
这里,”set things up” 部分可以是打开一个文件,或者获取某种外部资源;”tear things down” 部分则可以是关闭文件,释放或者移除资源。try-finally 结构保证即使是 try 内的代码没有正常完成执行,”tear things down” 部分总是可以执行。
如果你经常这么做,那么你可以把 “set things up” 和 “tear things down” 部分的代码放入一个库函数,从而更容易重用。你当然可以类似这么做:
def controlled_execution(callback):
set things up
try:
callback(thing)
finally:
tear things down
def my_function(thing):
do something
controlled_execution(my_function)
但是上述代码有点冗长,特别是你需要修改局部变量时。另一个方法是使用一个one-shot 生成器,然后使用 for-in 语句来“包装”(wrap)代码:
def controlled_execution():
set things up
try:
yield thing
finally:
tear things down
for thing in controlled_execution():
do something with thing
但是在 2.4 和更早版本中,yield 甚至不被允许出现在 try-finally 中。虽然这可以被修复(而且在 2.5 中这已经被修复了),但是当你知道你只想执行一次代码,使用循环结构就有点奇怪了。所以在考虑了一些替代方案后,GvR 和 python 开发团队最终想出了一个更通用的方法,即使用一个对象而不是一个生成器来控制外部代码的行为:
class controlled_execution:
def __enter__(self):
set things up
return thing
def __exit__(self, type, value, traceback):
tear things down
with controlled_execution() as thing:
some code
现在,当 “with” 语句被执行时, Python 将会对表达式求值,然后对结果值(又称为 “context guard”)调用 __enter__ 方法,接着将 __enter__ 返回的值赋值给 as 语句后给出的变量。随后,Python 将会执行代码主体(即 some code 部分);最后不论代码主体中发生了什么,都将会调用警卫对象(guard object)的 __exit__ 方法。
作为额外的奖励,__exit__ 方法可以查看异常(如果有的话),并且必要时可以压制(surpress)或者处理它。为了压制异常,只需要返回一个真值。如,以下__exit__ 方法可以忽略任何 TypeError,但是允许其他所有异常通过:
def __exit__(self, type, value, traceback):
return isinstance(value, TypeError)
在 Python 2.5中,文本对象已经配备了 __enter__ 和 __exit__ 方法;前者只返回文本对象本身,而后者则关闭文本。
>>> f = open("x.txt")
>>> f
<_io.TextIOWrapper name='x.txt' mode='r' encoding='cp936'>
>>> f.__enter__()
<_io.TextIOWrapper name='x.txt' mode='r' encoding='cp936'>
>>> f.read(1)
'X'
>>> f.__exit__(None, None, None)
>>> f.read(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file
所以为了打开文件,处理其内容,并且确保能关闭它,你可以简单地这么做:
with open("x.txt") as f:
data = f.read()
do something with data
总结:
with 语句能自动调用对象的 __enter__ 方法,完成 “set things up” 部分;在结束操作后会自动调用 __exit__ 方法完成 “tear things down” 部分。 而文本对象已经定义了 __enter__ 和 __exit__ 方法,所以我们可以直接使用 with 语句对文本对象进行操作,而不用关心关闭文本对象等操作。
事实上,像文本对象这样有 __enter__ 和 __exit__ 方法又可以称为上下文管理器(context manager)。当然,我们可以自定义自己的上下文管理器:定义__enter__ 方法,完成 “set things up”;定义 __exit__ 方法,完成 “tear things down” 部分。然后就可以使用 with 语句调用了。
可以参考这篇博客中的例子,定义我们自己的上下文管理器来处理文件:
class File():
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.open_file = open(self.filename, self.mode)
return self.open_file
def __exit__(self, *args):
self.open_file.close()
files = []
for _ in range(10000):
with File('foo.txt', 'w') as infile:
infile.write('foo')
files.append(infile)
Previous post: 理解 Python 的 for 语句
Next post: 理解 Python 的生成器

浙公网安备 33010602011771号