with上下文管理器
术语
要使用 with 语句,首先要明白上下文管理器这一概念。有了上下文管理器,with 语句才能工作。
下面是一组与上下文管理器和with 语句有关的概念。
上下文管理协议(Context Management Protocol):包含方法 __enter__() 和 __exit__(),支持
该协议的对象要实现这两个方法。
上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了
__enter__() 和 __exit__() 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,
负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,
也可以通过直接调用其方法来使用。
运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 __enter__() 和
__exit__() 方法实现,__enter__() 方法在语句体执行之前进入运行时上下文,__exit__() 在
语句体执行完后从运行时上下文退出。with 语句支持运行时上下文这一概念。
上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式
要返回一个上下文管理器对象。
语句体(with-body):with 语句包裹起来的代码块,在执行语句体之前会调用上下文管
理器的 __enter__() 方法,执行完语句体之后会执行 __exit__() 方法。
contextlib 模块
contextlib 模块提供了3个对象:装饰器 contextmanager、函数 nested 和上下文管理器 closing。使用这些对象,可以对已有的生成器函数或者对象进行包装,加入对上下文管理协议的支持,避免了专门编写上下文管理器来支持 with 语句。
装饰器 contextmanager
contextmanager 用于对生成器函数进行装饰,生成器函数被装饰以后,返回的是一个上下文管理器,其 __enter__() 和 __exit__() 方法由 contextmanager 负责提供,而不再是之前的迭代子。被装饰的生成器函数只能产生一个值,否则会导致异常 RuntimeError;产生的值会赋值给 as 子句中的 target,如果使用了 as 子句的话。
示例1代码:
#-*- coding:utf-8 -*- import contextlib import Queue q=Queue.Queue(5) @contextlib.contextmanager def work(li,val): #追加val到列表 li.append(val) try: #遇到yield函数将切回with方法执行print(li) yield finally: li.remove(val) li=[] q.put("aaa") #进入上下文管理器 with work(li,"renwu"):#这里由work方法先处理 print(li) q.get()#获取队列中的任务,获取到后with将继续执行work方法中yield下半部分代码li.remove(val) print(li)
利用yield返回值来处理
1 #-*- coding:utf-8 -*- 2 import contextlib 3 import Queue 4 5 @contextlib.contextmanager 6 def myopen(file,mode): 7 f=open(file,mode,encoding="utf-8") 8 try: 9 yield f 10 finally: 11 f.close() 12 13 #as f = yield f ,yield的返回一个f对象 14 with myopen('xxx.txt','r') as f: 15 print(f.readline())
示例2代码(class实现):
1 #-*- coding:utf-8 -*- 2 import contextlib 3 import Queue 4 5 q=Queue.Queue(5) 6 7 #自定义类 8 class work_with(object): 9 def __init__(self,li,val): 10 self.val=val 11 li.append(val) 12 13 14 def hello(self): 15 print("hello") 16 17 #__enter__ 这个结果是赋给 as f 18 def __enter__(self): 19 return self 20 21 22 #*argv 会传递三个值 23 #with最后执行的是 __exit__方法 24 def __exit__(self,*argv): 25 print(argv) 26 li.remove(self.val) 27 28 29 30 li=[] 31 q.put("aaa") 32 33 #with直接调用类的方式执行 34 with work_with(li,"hello") as f: 35 f.hello() 36 q.get()
with expression as variable:
with block
该代码快的执行过程是: * 1.先执行expression,然后执行该表达式返回的对象实例的__enter__函数,然后将该函数的返回值赋给as后面的变量。(注意,是将__enter__函数的返回值赋给变量) * 2.然后执行with block代码块,不论成功,错误,异常,在with block执行结束后,会执行第一步中的实例的__exit__函数。)
函数 nested
nested 可以将多个上下文管理器组织在一起,避免使用嵌套 with 语句。
清单 11. nested 语法
with nested(A(), B(), C()) as (X, Y, Z): # with-body code here
类似于:
. nested 执行过程
with A() as X: with B() as Y: with C() as Z: # with-body code here
需要注意的是,发生异常后,如果某个上下文管理器的 __exit__() 方法对异常处理返回 False,则更外层的上下文管理器不会监测到异常。
上下文管理器 closing
closing 的实现如下:
上下文管理 closing 实现
class closing(object): # help doc here def __init__(self, thing): self.thing = thing def __enter__(self): return self.thing def __exit__(self, *exc_info): self.thing.close()
上下文管理器会将包装的对象赋值给 as 子句的 target 变量,同时保证打开的对象在 with-body 执行完后会关闭掉。closing 上下文管理器包装起来的对象必须提供 close() 方法的定义,否则执行时会报 AttributeError 错误。
自定义支持 closing 的对象
class ClosingDemo(object): def __init__(self): self.acquire() def acquire(self): print 'Acquire resources.' def free(self): print 'Clean up any resources acquired.' def close(self): self.free() with closing(ClosingDemo()): print 'Using resources'
结果输出如下:
自定义 closing 对象的输出结果
Acquire resources.
Using resources
Clean up any resources acquired.