Python 的 with 语句(2)

我们经常既想利用with语句的便利,又不想很麻烦的写一个类来实现__enter__() 和 __exit__()方法,有什么比较现成的办法呢?

Python的标准库里引入了contextlib 模块可以解决这个问题。contextlib 模块提供了装饰器contextmanager,使用这个,可以对已有的生成器函数或者对象进行包装,加入对上下文管理协议的支持,避免了专门编写上下文管理器来支持 with 语句。

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

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__() 方法时会执行这些语句以获取/释放资源,即生成器函数中需要实现必要的逻辑控制,包括资源访问出现错误时抛出适当的异常。

贴一下contextmanager的实现。用的时候就当成一个修饰器(decorator)来用。

def contextmanager(func):
    """@contextmanager decorator.

    Typical usage:

        @contextmanager
        def some_generator(<arguments>):
            <setup>
            try:
                yield <value>
            finally:
                <cleanup>

    This makes this:

        with some_generator(<arguments>) as <variable>:
            <body>

    equivalent to this:

        <setup>
        try:
            <variable> = <value>
            <body>
        finally:
            <cleanup>

    """
    @wraps(func)
    def helper(*args, **kwds):
        return GeneratorContextManager(func(*args, **kwds))
    return helper

另外说一句。with 语句的管理上下文的能力在python中往往被定义成设计模式的一种。下面就是一个例子。其实就是实现上下文管理协议,但是名字叫ResourceAcquisitionIsInitialization(资源获取初始化)。只贴代码,不另外做解释了。

class Box(object):
    
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print("Box " + self.name + " Opened")
        return self

    def __exit__(self, exception_type, exception, traceback):
        all_none = all(
            arg is None for arg in [exception_type, exception, traceback]
            )
        if (not all_none):
            print("Exception: \"%s\" raised." %(str(exception)))
        print("Box Closed")
        print("")
        return all_none

#===============================================================
if (__name__ == "__main__"):
    with Box("tupperware") as simple_box:
        print("Nothing in " + simple_box.name)
    with Box("Pandora's") as pandoras_box:
        raise Exception("All the evils in the world")
    print("end")

-EOF-

发表评论

电子邮件地址不会被公开。 必填项已用*标注