用 Python 作一件很日常的事情: 打开文件, 逐行读入, 最后关掉文件; 进一步的需求是, 这也许是程序中一个可选的功能, 若是有任何问题, 好比文件没法打开, 或是读取出错, 那么在函数内须要捕获全部异常, 输出一行警告并退出. 代码可能一开始看起来是这样的
html
def read_file(): try: f = open('yui', 'r') print ''.join(f.readlines()) except: print 'error occurs while reading file' finally: f.close()
不过这显然没法运做, 由于 f 是在 try 块中定义的, 而在 finally 中没法引用.
若是将 f 提取到 try 块外部, 如
python
def read_file(): f = open('azusa', 'r') try: print ''.join(f.readlines()) except: print 'error occurs while reading file' finally: f.close()
那么, 问题在于当打开文件失败, 抛出异常将不会被捕获.
挫一点的方法天然是, 再套一层 try 吧
express
def read_file(): try: f = open('sawako', 'r') try: print ''.join(f.readlines()) except: print 'error occurs while reading file' finally: f.close() except: print 'error occurs while reading file'
固然这不单单是多一层缩进挫了, 连警告输出都白白多一次呢.
正规一点的方式是, 使用 Python 引入的 with 结构来解决, 如
app
def readFile(): try: with open('mio', 'r') as f: print ''.join(f.readlines()) except: print 'error occurs while reading file'
当文件打开失败时, 异常天然会被 except 到; 不然, 在 with 块结束以后, 打开的文件将自动关闭.
除了打开文件, 还有其它这样能够用于 with 的东西么? 或者说, 怎么自定义一个什么东西, 让它能用于 with 呢?
直接回答后一个问题吧, 秘密在于 Python 虚拟机在 with 块退出时会去寻找对象的 __exit__ 方法并调用它, 把释放资源的动做放在这个 __exit__ 函数中就能够了; 另外, 对象还须要一个 __enter__ 函数, 当进入 with 块时, 这个函数被调用, 而它的返回值将做为 as 后引用的值. 一个简单的例子是
函数
class Test: def __init__(self): print 'init' def __enter__(self): print 'enter' return self def __exit__(self, except_type, except_obj, tb): print except_type print except_obj import traceback print ''.join(traceback.format_tb(tb)) print 'exit' return True with Test() as t: raise ValueError('kon!')
执行这一段代码, 输出将会是
ui
init enter <type 'exceptions.ValueError'> kon! File "test.py", line 17, in <module> raise ValueError('kon!') exit
__exit__ 函数接受三个参数, 分别是异常对象类型, 异常对象和调用栈. 若是 with 块正常退出, 那么这些参数将都是 None . 返回 True 表示发生的异常已被处理, 再也不继续向外抛出.
url
简单的介绍到此为止, 详细的状况能够参考 PEP 343 (这数字真不错, 7 3 ). spa
下面介绍下 with 语句的实例用法 & 高级用法: 线程
Python高端、大气、上档次的with语句 设计
在说with语句以前,先看看一段简单的代码吧
lock = threading.Lock() ... lock.acquire() elem = heapq.heappop(heap) lock.release()
很简单直观,多个线程共用一个优先级队列的时候,首先先用互斥锁lock.acquire()把优先级队列锁上,而后取元素,再而后lock.release()释放这个锁。
虽然看似很是符合逻辑的一个过程,可是里面隐藏着一个巨大的bug:当heap里面没有元素的时候,会抛出一个IndexError异常,再而后堆栈回滚,再而后lock.release()根本不会执行,这个锁就永远得不到释放,所以就发生了喜闻乐见的死锁问题。这个也是不少大神们讨厌异常的缘由。经典Java风格的解决方案就是
lock = threading.Lock() ... lock.acquire() try: elem = heapq.heappop(heap) finally: lock.release()
这个虽然能够,可是怎么看怎么dirty,和Python优雅、简单的风格出入很大。其实,自从Python2.5开始引入了with语句,一切就变得很是简单:
lock = threading.Lock() ... with lock: elem = heapq.heappop(heap)
在此不管以何种方式离开with语句的代码块,锁都会被释放。
with语句的设计目的就是为了使得以前须要经过try...finally解决的清理资源问题变得简单、清晰,它的的用法是
with expression [as variable]: with-block
其中expression返回一个叫作「context manager」的对象,而后这个对象被赋给variable(若是有的话)。「context manager」对象有两个方法,分别是__enter__()和__exit__(),很明显一个在进入with-block时调用,一个离开with-block的时候调用。
这样的对象不须要本身去实现,在Python标准库里面不少API都是已经实现了这两个方法,最多见的一个例子就是读写文件的open语句。
with open('1.txt', encoding = 'utf-8') as fp: lines = fp.readlines()
不管是正常离开仍是由于异常缘由离开with语句块,打开的文件资源老是会释放。
接下去讨论一下with语句配合contextlib库的一些比较实用的方法,好比须要同时打开两个文件,一个读一个写,这个时候就能够这样写:
from contextlib import nested ... with nested(open('in.txt'), open('out.txt', 'w')) as (fp_in, fp_out): ...
这样就能够省掉两个with的语句的嵌套了,另外若是遇到一些尚未支持「context manager」的API呢?好比urllib.request.urlopen(),这个返回的对象由于不是「context manager」,结束的时候还须要本身去调用close方法。
相似这种API,contextlib提供了一个叫作closing方法,它会在离开with语句的时候,自动调用对象的close方法,所以urlopen也能够这样写:
from contextlib import closing ... with closing(urllib.request.urlopen('http://www.yahoo.com')) as f: for line in f: sys.stdout.write(line)
参考资料:
[1] 8 PEP 343: The 'with' statement
https://docs.python.org/release/2.5/whatsnew/pep-343.html
Refer:http://ling0322.info/2013/10/03/python-with-statement.html
[2] Python 上下文管理器