2.1.3.2 使用生成器定义上下文管理器

当讨论生成器时,曾说过与循环相比,我们更偏好将生成器实现为一个类,因为,他们更短、更美妙,状态存储在本地,而不是实例和变量。另一方面,就如在双向沟通中描述的,生成器和它的调用者之间的数据流动可以是双向的。这包含异常,可以在生成器中抛出。我们希望将上下文生成器实现为一个特殊的生成器函数。实际上,生成器协议被设计成可以支持这个用例。

In [ ]:

  1. @contextlib.contextmanager
  2. def some_generator(<arguments>):
  3. <setup>
  4. try:
  5. yield <value>
  6. finally:
  7. <cleanup>

contextlib.contextmanager帮助者可以将一个生成器转化为上下文管理器。生成器需要遵循一些封装器函数强加的规则—它必须yield一次。在yield之前的部分是从enter来执行,当生成器在yield挂起时,由上下文管理器保护的代码块执行。如果抛出异常,解释器通过exit参数将它交给封装器,然后封装器函数在yield语句的点抛出异常。通过使用生成器,上下文管理器更短和简单。

让我们将closing例子重写为一个生成器:

In [ ]:

  1. @contextlib.contextmanager
  2. def closing(obj):
  3. try:
  4. yield obj
  5. finally:
  6. obj.close()

让我们将assert_raises例子重写为生成器:

In [ ]:

  1. @contextlib.contextmanager
  2. def assert_raises(type):
  3. try:
  4. yield
  5. except type:
  6. return
  7. except Exception as value:
  8. raise AssertionError('wrong exception type')
  9. else:
  10. raise AssertionError('exception expected')

这里我们使用修饰器来将一个生成器函数转化为上下文管理器!

In [1]:

  1. %matplotlib inline
  2. import numpy as np