9.22 定义上下文管�器的简�方法¶

问题¶

你想自己去实现一个新的上下文管�器,以便使用with语�。

解决方案¶

实现一个新的上下文管�器的最简�的方法就是使用 contexlib 模�中的 @contextmanager 装饰器。 下�是一个实现了代��计时功能的上下文管�器例�:

import time
from contextlib import contextmanager

@contextmanager
def timethis(label):
    start = time.time()
    try:
        yield
    finally:
        end = time.time()
        print('{}: {}'.format(label, end - start))

# Example use
with timethis('counting'):
    n = 10000000
    while n > 0:
        n -= 1

在函数 timethis() 中,yield 之�的代�会在上下文管�器中作为 __enter__() 方法执行, 所有在 yield 之�的代�会作为 __exit__() 方法执行。 如果出现了异常,异常会在yield语�那里抛出。

下�是一个更加高级一点的上下文管�器,实现了列表对象上的��事务:

@contextmanager
def list_transaction(orig_list):
    working = list(orig_list)
    yield working
    orig_list[:] = working

这段代�的作用是任何对列表的修改�有当所有代��行完�并且�出现异常的情况下�会生效。 下�我们�演示一下:

>>> items = [1, 2, 3]
>>> with list_transaction(items) as working:
...     working.append(4)
...     working.append(5)
...
>>> items
[1, 2, 3, 4, 5]
>>> with list_transaction(items) as working:
...     working.append(6)
...     working.append(7)
...     raise RuntimeError('oops')
...
Traceback (most recent call last):
    File "<stdin>", line 4, in <module>
RuntimeError: oops
>>> items
[1, 2, 3, 4, 5]
>>>

讨论¶

通常情况下,如果�写一个上下文管�器,你需�定义一个类,里�包�一个 __enter__() 和一个 __exit__() 方法,如下所示:

import time

class timethis:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        self.start = time.time()

    def __exit__(self, exc_ty, exc_val, exc_tb):
        end = time.time()
        print('{}: {}'.format(self.label, end - self.start))

尽管这个也�难写,但是相比较写一个简�的使用 @contextmanager 注解的函数而言还是�显�味。

@contextmanager 应该仅仅用�写自包�的上下文管�函数。 如果你有一些对象(比如一个文件�网络连接或�),需�支� with 语�,那么你就需��独实现 __enter__() 方法和 __exit__() 方法。