with上下文管理器

时间:2023-03-09 17:43:44
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返回值来处理

 #-*- coding:utf-8 -*-
import contextlib
import Queue @contextlib.contextmanager
def myopen(file,mode):
f=open(file,mode,encoding="utf-8")
try:
yield f
finally:
f.close() #as f = yield f ,yield的返回一个f对象
with myopen('xxx.txt','r') as f:
print(f.readline())

myopen

示例2代码(class实现):

 #-*- coding:utf-8 -*-
import contextlib
import Queue q=Queue.Queue(5) #自定义类
class work_with(object):
def __init__(self,li,val):
self.val=val
li.append(val) def hello(self):
print("hello") #__enter__ 这个结果是赋给 as f
def __enter__(self):
return self #*argv 会传递三个值
#with最后执行的是 __exit__方法
def __exit__(self,*argv):
print(argv)
li.remove(self.val) li=[]
q.put("aaa") #with直接调用类的方式执行
with work_with(li,"hello") as f:
f.hello()
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.
closing 适用于提供了 close() 实现的对象,比如网络连接、数据库连接等,也可以在自定义类时通过接口 close() 来执行所需要的资源“清理”工作。