[译]了解Python的with语句

在 Sun 08 March 2015 发布于 Python 分类

这篇文章虽然有些时日, 但是觉得这篇博文通俗易懂, 翻译起来也没有太大的压力, 然后就有了篇译文. 初次翻译, 如有错误, 请告知, 谢谢!

从comp.lang.python和其他论坛来看, 有经验的python开发者对python2.5新的with语句(坏链接)似乎依旧有些困惑.

就像python中的其他大部分的特性, 只要你弄懂了with 语句想要解决的问题, 你就会知道with语句其实是很简单的. 看看下面这段代码:

set things up
try:
    do something
finally:
    tear things down

‘初始设置’ 可能是打开一个文件, 或者是获取一些额外的资源, ‘清理阶段’可能是关闭一个文件, 释放或者归还资源. try-finally结构保证 '清理阶段'部分是一定被执行,尽管 主要逻辑部分的代码并没有把要做的工作做完

当有大量这种重复性的工作时, 把'初始设置' 和 '清理阶段'封装成库是很便利, 也易于复用. 你可能会做如下封装:

def controlled_execution(callback):
set things up
try:
    callback(things)
finally:
    tear things down

def my_function(thing):
    do something

controlled_execution(my_function)

但是, 代码看起来还是有点啰嗦, 特别当需要修改局部变量. 另一种方法是使用生成器函数, 并使用for语句进行封装:

def controlled_execution():
    set things up
    try:
        yield thing
    finally:
        tear things down

for thing in controlled_execution():
    do something with things

但是在Python2.4及更早的版本, yield出现在try-finally语句中是错误的语法. 虽然这个会在后面被修复(实际上在Python2.5版本加上了这特性). 但是明知道只需要执行一次却用循环来实现逻辑, 感觉总是有些怪怪的!

在考察了一些备选方案后, 最终GvR和python-dev最终提出了对上面方法的泛化实现: 使用对象而不是使用生成器来控制代码的逻辑

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在解释该表达式时,会调用__enter__并返回其结果(称之为"上下文守卫"), 然后把 __enter__返回的值赋值给as提供的变量. Python然后会执行代码主体, 不管程序发生了什么都会执行__exit__守卫函数

额外说一点, 当有异常触发时, __exit__方法能够抑制异常,也可以让异常发生, 除非此异常很必要. 可以通过仅仅返回一个真值来抑制异常. 例如, 下面的代码中__exit__方法仅仅抑制了TypeError异常:

def __exit__(self, type, value, traceback):
    return isinstance(value, TypeError)

在 Python2.5, python的文件对象内置了__enter__和__exit__方法; 前者仅返回文件本身, 后者关闭文件:

 >>> f = open("x.txt")
    >>> f
    <open file 'x.txt', mode 'r' at 0x00AE82F0>
    >>> f.__enter__()
    <open file 'x.txt', mode 'r' at 0x00AE82F0>
    >>> 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

这并不难, 不是吗?