使用open打开过文件的对with/as都已经很是熟悉,其实with/as是对try/finally的一种替代方案。python
当某个对象支持一种称为"环境管理协议"的协议时,就会经过环境管理器来自动执行某些善后清理工做,就像finally同样:无论中途是否发生异常,最终都会执行某些清理操做。express
用法:安全
with expression [as var]: with_block_code
当expression返回的对象是支持环境管理协议的时候,就可使用with。as var是可选的,若是不使用as var,expression返回对象将被丢弃,若是使用as var,就会将expression的返回对象赋值给变量var。函数
整个流程大体以下:先评估expression,若是支持环境管理协议,而后开始with/as语句块结构,当准备退出with语句块的时候,将执行对象中定义的善后操做。工做机制的细节见下文。测试
例如,open()返回的文件对象是支持环境管理协议的,因此能够用with/as来安全地打开文件:ui
with open(r'd:\a\b\c\a.log') as logfile: for line in logfile: print(line) ...more code here...
整个过程是先open(),而后with/as,输出每一行后将要退出with语句块的时候,环境管理器根据文件对象中定义的操做关闭文件。this
它实际上等价于:code
myfile = open(r'd:\a\b\c\a.log') try: for line in myfile: print(line) ...more code here... finally: myfile.close()
虽然在文件不被引用以后,垃圾回收器会自动回收这个文件对象,可是垃圾回收器的回收操做是有等待时间的。换句话说,若是不使用with/as打开文件,也不显示close()关闭文件,那么这个文件极可能会在用完以后保持空闲一段时间,而后才被垃圾回收器回收。orm
with/as不只用于文件打开/关闭,锁操做也支持环境管理协议,也就是说,在有须要的时候会自动释放锁资源。对象
在python 3.1以后,with as支持多个环境管理器,使用逗号隔开便可。
with A() as a, B() as b: ...statements...
它等价于嵌套的with:
with A() as a: with B() as b: ...statements...
多环境管理器管理的多个对象会在with语句块中出现异常的时候,或者执行完with语句块的时候所有自动被清理(例如文件关闭操做)。
例如,打开两个文件,将它们的内容经过zip()合并在一块儿,而且同时关闭它们:
with open('a.file') as f1, open('b.file') as f2: for pair in zi[(f1, f2): print(pair)
不管是文件仍是锁,都是别人已经写好了环境管理器的对象。咱们本身也能够写环境管理器,让它可使用with/as,这实际上属于运算符重载的范畴。
要写本身的环境管理器,先了解with/as的工做机制的细节:
__enter__
和__exit__
方法,返回的对象称为"环境管理器"__enter__
方法。__enter__
方法的返回值赋值给 as 指定的变量,或者直接丢弃(没有使用as)__exit__(type,value,traceback)
方法,其中这3个和异常相关的参数来源于sys.exc_info
。若是__exit__
返回值为False,则会自动从新抛异常以便传播异常,不然异常被认为合理处理__exit__(None,None,None)
,即这三个参数都传递为None值看一个简单的示例:
class TraceBlock: def message(self, arg): print('running ' + arg) def __enter__(self): print('starting with block') return self def __exit__(self, exc_type, exc_value, exc_tb): if exc_type is None: print('exited normally\n') else: print('raise an exception! ' + str(exc_type)) return False
上面的__enter__
方法返回的对象会赋值给as关键字指定的变量,在这个示例中即将对象自身返回。若是有需求,能够返回其它对象。
上面的__exit__
中,若是异常的类型为None,说明with语句块中的语句执行过程没有抛异常,正常结束便可。可是若是有异常,则要求返回False,实际上上面的return False
能够去掉,由于函数没有return时默认返回None,它的布尔值表明的就时False。
测试下:
with TraceBlock() as action: action.message("test 1") print("reached") print('-' * 20, "\n") with TraceBlock() as action: action.message("test 2") raise TypeError print("not reached")
结果以下:
starting with block running test 1 reached exited normally -------------------- starting with block running test 2 raise an exception! <class 'TypeError'> Traceback (most recent call last): File "g:/pycode/list.py", line 23, in <module> raise TypeError TypeError
定义环境管理器不是件简单的事。通常来讲,若是不是很复杂的需求,直接使用try/finally来定义相关操做便可。
在自定义上下文管理器的时候,能够经过contextlib模块的contextmanager装饰器来简化,这样不须要本身去写__enter__
和__exit__
。
使用contextlib.contextmanager
装饰的时候,所装饰的对象必须是一个生成器函数,且该生成器函数必须只yield一个值,这个值将会被绑定到with/as的as子句的变量上。
from contextlib import contextmanager @contextmanager def managed_resource(*args, **kwds): # Code to acquire resource, e.g.: resource = acquire_resource(*args, **kwds) try: yield resource finally: # Code to release resource, e.g.: release_resource(resource) >>> with managed_resource(timeout=3600) as resource: ... # Resource is released at the end of this block, ... # even if code in the block raises an exception
它的执行流程是这样的:
managed_resource(timeout=3600)
,它是一个生成器函数,它会返回一个生成器resource = acquire_resource
,并在yield的地方状态被挂起这个生成器函数若是在with语句块中发生了异常且未处理,则会在生成器yield被恢复的时候再次抛出这个异常。所以,可使用try...except...finally语句来捕获可能存在的错误,以便保证yield后面的语句(一般是资源释放类的善后工做)能够正常执行。