with上下文管理器

时间:2021-07-05 00:12:10

术语

要使用 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代码:

with上下文管理器with上下文管理器
#-*- 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)
View Code

 

利用yield返回值来处理

with上下文管理器with上下文管理器
 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())
myopen

 

 

示例2代码(class实现):

with上下文管理器with上下文管理器
 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()
View Code
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() 来执行所需要的资源“清理”工作。