9

在我的代码中,我需要能够正确打开和关闭设备,因此需要使用上下文管理器。虽然上下文管理器通常被定义为具有__enter____exit__方法的类,但似乎也有可能装饰一个函数以与上下文管理器一起使用(参见最近的一篇文章另一个很好的例子)。

在以下(工作)代码片段中,我实现了两种可能性;只需要将注释行与另一行交换:

import time
import contextlib

def device():
    return 42

@contextlib.contextmanager
def wrap():
    print("open")
    yield device
    print("close")
    return

class Wrap(object):
    def __enter__(self):
        print("open")
        return device
    def __exit__(self, type, value, traceback):
        print("close")


#with wrap() as mydevice:
with Wrap() as mydevice:
    while True:
        time.sleep(1)
        print mydevice()

我尝试的是运行代码并使用CTRL-C. 当我Wrap在上下文管理器中使用该类时,该__exit__方法按预期调用(在终端中打印文本“关闭”),但是当我使用该wrap函数尝试相同的操作时,文本“关闭”不会打印到终端。

我的问题:代码片段是否有问题,我是否遗漏了什么,或者为什么print("close")没有使用装饰函数调用该行?

4

1 回答 1

17

文档中的示例contextmanager有点误导。之后的函数部分yield并不真正对应于__exit__上下文管理器协议的。文档中的关键点是:

如果块中发生未处理的异常,它会在生成器中在产生产生的点重新引发。因此,您可以使用try...except...finally语句来捕获错误(如果有),或确保进行一些清理。

因此,如果您想在 contextmanager 装饰的函数中处理异常,您需要编写自己的try包装yield并自己处理异常,在 a 中执行清理代码finally(或者只是阻止异常并在except之后执行清理try/except)。例如:

@contextlib.contextmanager
def cm():
    print "before"
    exc = None
    try:
        yield
    except Exception, exc:
        print "Exception was caught"
    print "after"
    if exc is not None:
        raise exc

>>> with cm():
...     print "Hi!"
before
Hi!
after

>>> with cm():
...     print "Hi!"
...     1/0
before
Hi!
Exception was caught
after

此页面还显示了一个有启发性的示例。

于 2013-03-16T08:30:53.977 回答