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