为什么我的上下文管理器函数和类在Python中表现不同?
在我的代码中,我需要正确地打开和关闭一个设备,因此我觉得使用上下文管理器是很有必要的。上下文管理器通常是一个包含__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__
方法会按预期被调用(终端中会打印出'close'),但当我尝试用wrap
函数做同样的事情时,终端中却没有打印出'close'。
我的问题是:这个代码片段有什么问题吗?我是不是漏掉了什么,或者为什么用装饰器的函数没有调用print("close")
这一行?
1 个回答
17
文档中关于 contextmanager
的例子有点让人误解。函数中在 yield
之后的部分,其实并不完全对应于上下文管理器协议中的 __exit__
。文档中的关键点是:
如果在代码块中出现了未处理的异常,它会在生成器中重新抛出,位置正好是在
yield
的地方。因此,你可以使用try...except...finally
语句来捕获错误(如果有的话),或者确保进行一些清理工作。
所以,如果你想在使用 contextmanager
装饰的函数中处理异常,你需要自己写一个 try
,把 yield
包裹起来,并自己处理异常,在 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
这个页面也展示了一个很有启发性的例子。