详解Python中contextlib上下文管理模块的用法
咱们用的os模块,读取文件的时候,其实他是含有__enter____exit__。 一个是with触发的时候,一个是退出的时候。
withfile('nima,'r')asf: printf.readline()
那咱们自己再实现一个标准的可以with的类。我个人写python的时候,喜欢针对一些需要有关闭逻辑的代码,构造成with的模式。
#encoding:utf-8 classecho: def__enter__(self): print'enter' def__exit__(self,*args): print'exit' withecho()ase: print'nima'
contextlib是个比with优美的东西,也是提供上下文机制的模块,它是通过Generator装饰器实现的,不再是采用__enter__和__exit__。contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制。
fromcontextlibimportcontextmanager @contextmanager defmake_context(): print'enter' try: yield{} exceptRuntimeError,err: print'error',err finally: print'exit' withmake_context()asvalue: printvalue
我这里再贴下我上次写的redis分布式锁代码中有关于contextlib的用法。其实乍一看,用了with和contextlib麻烦了,但是最少让你的主体代码更加鲜明了。
fromcontextlibimportcontextmanager fromrandomimportrandom DEFAULT_EXPIRES=15 DEFAULT_RETRIES=5 @contextmanager defdist_lock(key,client): key='lock_%s'%key try: _acquire_lock(key,client) yield finally: _release_lock(key,client) def_acquire_lock(key,client): foriinxrange(0,DEFAULT_RETRIES): get_stored=client.get(key) ifget_stored: sleep_time=(((i+1)*random())+2**i)/2.5 print'Sleeipngfor%s'%(sleep_time) time.sleep(sleep_time) else: stored=client.set(key,1) client.expire(key,DEFAULT_EXPIRES) return raiseException('Couldnotacquirelockfor%s'%key) def_release_lock(key,client): client.delete(key)
ContextManagerAPI
一个上下文管理器通过with声明激活,而且API包含两个方法。__enter__()方法运行执行流进入到with代码块内。他返回一个对象共上下文使用。当执行流离开with块时,__exit__()方法上下文管理器清除任何资源被使用。
classContext(object): def__init__(self): print'__init__()' def__enter__(self): print'__enter__()' returnself def__exit__(self,exc_type,exc_val,exc_tb): print'__exit__()' withContext(): print'Doingworkinthecontext.'
打印结果
__init__() __enter__() Doingworkinthecontext. __exit__()
执行上下文管理器时会调用__enter__离开时调用__exit__。
__enter__能返回任意对象,联合一个指定名字于with声明。
classWithinContext(object): def__init__(self,context): print'WithinContext.__init__(%s)'%context defdo_something(self): print'WithinContext.do_something()' def__del__(self): print'WithinContext.__del__' classContext(object): def__init__(self): print'__init__()' def__enter__(self): print'__enter__()' returnWithinContext(self) def__exit__(self,exc_type,exc_val,exc_tb): print'__exit__()' withContext()asc: c.do_something()
打印结果
__init__() __enter__() WithinContext.__init__(<__main__.Contextobjectat0x7f579d8e4890>) WithinContext.do_something() __exit__() WithinContext.__del__
如果上下文管理器能处理异常,__exit__()应该返回一个True值表明这个异常不需要传播,返回False异常会在执行__exit__之后被引起。
classContext(object): def__init__(self,handle_error): print'__init__(%s)'%handle_error self.handle_error=handle_error def__enter__(self): print'__enter__()' returnself def__exit__(self,exc_type,exc_val,exc_tb): print'__exit__(%s,%s,%s)'%(exc_type,exc_val,exc_tb) returnself.handle_error withContext(True): raiseRuntimeError('errormessagehandled') print withContext(False): raiseRuntimeError('errormessagepropagated')
打印结果
__init__(True) __enter__() __exit__(<type'exceptions.RuntimeError'>,errormessagehandled,<tracebackobjectat0x7fdfb32f8b00>) __init__(False) __enter__() __exit__(<type'exceptions.RuntimeError'>,errormessagepropagated,<tracebackobjectat0x7fdfb32f8b90>) Traceback(mostrecentcalllast): File"test.py",line23,in<module> raiseRuntimeError('errormessagepropagated') RuntimeError:errormessagepropagated
从生成器到上下文管理器
创建上下文管理的传统方法,通过编写一个类与__enter__()和__exit__()方法,并不困难。但有时比你需要的开销只是管理一个微不足道的上下文。在这类情况下,您可以使用contextmanager()decorator生成器函数转换成一个上下文管理器。
importcontextlib @contextlib.contextmanager defmake_context(): print'entering' try: yield{} exceptRuntimeError,err: print'Error:',err finally: print'exiting' print'Normal:' withmake_context()asvalue: print'insidewithstatement:',value print print'handledereor:' withmake_context()asvalue: raiseRuntimeError('showexampleofhandlinganerror') print print'unhandlederror:' withmake_context()asvalue: raiseValueError('thisexceptionisnothandled')
打印结果
Normal: entering insidewithstatement:{} exiting handledereor: entering Error:showexampleofhandlinganerror exiting unhandlederror: entering exiting Traceback(mostrecentcalllast): File"test.py",line30,in<module> raiseValueError('thisexceptionisnothandled') ValueError:thisexceptionisnothandled
嵌套上下文
使用nested()可以同时管理多个上下文。
importcontextlib @contextlib.contextmanager defmake_context(name): print'entering:',name yieldname print'exiting:',name withcontextlib.nested(make_context('A'),make_context('B'),make_context('C'))as(A,B,C): print'insidewithstatement:',A,B,C
打印结果
entering:A entering:B entering:C insidewithstatement:ABC exiting:C exiting:B exiting:A
因为Python2.7和以后的版本不赞成使用nested(),因为可以直接嵌套
importcontextlib @contextlib.contextmanager defmake_context(name): print'entering:',name yieldname print'exiting:',name withmake_context('A')asA,make_context('B')asB,make_context('C')asC: print'insidewithstatement:',A,B,C
关闭open的句柄
文件类支持上下文管理器,但是有一些对象不支持。还有一些类使用close()方法但是不支持上下文管理器。我们使用closing()来为他创建一个上下文管理器。(类必须有close方法)
importcontextlib classDoor(object): def__init__(self): print'__init__()' defclose(self): print'close()' print'NormalExample:' withcontextlib.closing(Door())asdoor: print'insidewithstatement' print print'Errorhandlingexample:' try: withcontextlib.closing(Door())asdoor: print'raisingfrominsidewithstatement' raiseRuntimeError('errormessage') exceptException,err: print'Hadanerror:',err
打印结果
NormalExample: __init__() insidewithstatement close() Errorhandlingexample: __init__() raisingfrominsidewithstatement close() Hadanerror:errormessage