Python 中高级知识 contextlib

contextlib 模块

contextlib 模块提供了3个对象:装饰器contextmanager、上下文管理器closing。使用这些对象,可以对已有的生成器函数或者对象进行包装,加入对上下文管理协议的支持,避免了专门编写上下文管理器来支持with语句。同时我们也是要函数ExitStack/nested做些思维发散

contextlib源码片段

__all__ = ["asynccontextmanager""contextmanager""closing""nullcontext",
           "AbstractContextManager""AbstractAsyncContextManager",
           "AsyncExitStack""ContextDecorator""ExitStack",
           "redirect_stdout""redirect_stderr""suppress"]

装饰器 contextmanager

contextmanager用于对生成器函数进行装饰,生成器函数被装饰以后,返回的是一个上下文管理器,其 __enter__()__exit__()方法由contextmanager负责提供,而不再是之前的迭代子。被装饰的生成器函数只能产生一个值,否则会导致异常RuntimeError;产生的值会赋值给as子句中的target,如果使用了as子句的话。

下面看一个简单的例子:

# !/usr/bin/env python3
# -*- coding: utf-8 -*-
 
 
from contextlib import contextmanager
 
 
@contextmanager
def demo():
    print('[Allocate resources]')
    print('Code before yield-statement executes in __enter__')
    yield '*** contextmanager demo ***'
    print('Code after yield-statement executes in __exit__')
    print('[Free resources]')
 
 
with demo() as value:
    print('Assigned Value: %s' % value)
[Allocate resources]
Code before yield-statement executes in __enter__
Assigned Value: *** contextmanager demo ***
Code after yield-statement executes in __exit__
[Free resources]

可以看到,生成器函数中 yield 之前的语句在__enter__()方法中执行,yield 之后的语句在__exit__()中执行,而 yield 产生的值赋给了 as 子句中的 value 变量。

需要注意的是,contextmanager只是省略了__enter__()/__exit__()的编写,但并不负责实现资源的“获取”和“清理”工作;“获取”操作需要定义在yield语句之前,“清理”操作需要定义 yield 语句之后,这样with语句在执行__enter__()/__exit__()方法时会执行这些语句以获取/释放资源,即生成器函数中需要实现必要的逻辑控制,包括资源访问出现错误时抛出适当的异常。

函数 ExitStack/nested

错误修正 link

python3.3 以后: ExitStack
python3.3以前: nested 可以将多个上下文管理器组织在一起,避免使用嵌套 with 语句。

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() 来执行所需要的资源“清理”工作。

posted @ 2021-09-26 10:40  旁人X  阅读(527)  评论(0)    收藏  举报