理解python的with语句

  有一些任务, 可能事先需要设置, 事后做清理工作. 对于这种场景, python的with语句提供了一种非常方变的处理方式, 一个很好的例子是文件处理. 你需要获取一个文件的句柄, 从文件中读取数据, 然后关闭文件句柄. 如果不用with语句, 代码如下:
file = open("/tmp/foo.txt")
data = file.read()
file.close()

这里有两个比较烦人的地方, 一是可能忘记关闭句柄, 而是文件读取数据时发生异常, 却没有进行任何处理(文件读取数据发生异常时, 后面的代码将得不到执行, 因此获取的文件句柄不能正常关闭), 下面的代码可以解决这个问题:

file = open("/tmp/foo.txt")
try:
    data = file.read()
finally:
    file.close()

虽然这段代码工作良好, 但是太冗长了, 这时候就是with出马的时候了. 用with写法, 除了拥有更优雅的语法, 还能够处理异常. 下面是上面代码的with写法:

with open("/tmp/foo.txt") as file:
    data = file.read()

它是怎么工作的呢?

  基本思想是with关键字后面的语句生成的对象必须实现两个方法, __enter__()和__exit__()方法. with关键字后面的语句被求值后, 返回对象的__enter__()方法被调用, 这个方法的返回值被赋值为as关键字后面的变量. 当with语句后面的代码全部执行完之后, 将调用前面返回对象的__exit__()对象. 下面这个例子可以说明with语句如何工作:

class Sample:
    def __enter__(self):
        print "In __enter__()"
        return "foo"
    def __exit__(self, type, value, trace):
        print "In __exit__()"
def get_sample():
    return Sample()
with get_sample() as sample:
    print "Sample:", sample

上面的代码执行结果如下:

In __enter__()
Sample: foo
In __exit__()

正如你所看到的,

1. __enter__()方法被执行

2. __enter__()方法返回的值(这里是"foo")被赋值给变量sample

3. 执行with语句后面的代码块, 打印sample变量的值

4. __exit__()方法被执行

  with语句真正强大之处在于它可以处理异常, 你应该已经注意到了Sample类的__exit__()方法有三个参数, 分别是type, value和trace. 这三个参数在异常处理时非常有用, 我们来改一下代码, 看看到底是怎么工作的:

class Sample:
    def __enter__(self):
        return self
    def __exit__(self, type, value, trace):
        print "type:", type
        print "value:", value
        print "trace:", trace
    def do_something(self):
        bar = 1/0
        return bar + 10
def get_sample():
    return Sample()
with get_sample() as sample:
    sample.do_something()

执行后的结果如下:

type: <type 'exceptions.ZeroDivisionError'>
value: integer division or modulo by zero
trace: <traceback object at 0xb749c11c>
Traceback (most recent call last):
  File "a.python", line 14, in <module>
    sample.do_something()
  File "a.python", line 9, in do_something
    bar = 1/0
ZeroDivisionError: integer division or modulo by zero

实际上, 在with语句后面的任何代码抛出异常时, __exit__()方法被执行, 即便不是因为Sample类里的方法产生的异常, 异常发生时, 与之关联的type, value和trace也会传给__exit__()方法. 因此抛出的ZeroDivisionError异常被打印出来了. 开发库时, 清理资源, 关闭文件等操作都可以放到__exit__()方法中.

因此, python的with语句可以让代码更简练, 而且处理异常更加简单.

原文参见

Understanding Python's "With" Statement
原文地址:https://www.cnblogs.com/iamswf/p/4703330.html