python如何在上下文管理器中安全地处理异常

时间:2015-01-26 20:11:15

标签: python exception-handling contextmanager

我想我已经读过with内的异常不允许__exit__正确调用。如果我在这个笔记上错了,请原谅我的无知。

所以我在这里有一些伪代码,我的目标是使用锁定上下文,在__enter__记录开始日期时间并返回锁定ID,并在__exit__记录结束日期时间并释放锁:

def main():
    raise Exception

with cron.lock() as lockid:
    print('Got lock: %i' % lockid)
    main()

除了安全地存在上下文之外,我怎么还能引发错误?

注意:我故意在此伪代码中引发基本异常,因为我想在任何异常时安全退出,而不仅仅是预期的异常。

注意:替代/标准并发防范方法无关紧要,我想将这些知识应用于任何一般的上下文管理。我不知道不同的上下文是否有不同的怪癖。

PS。 finally块是否相关?

2 个答案:

答案 0 :(得分:21)

如果上下文管理器被异常中断,则__exit__方法被称为正常。实际上,传递给__exit__的参数都与处理这种情况有关!来自the docs

  

object.__exit__(self, exc_type, exc_value, traceback)

     

退出与此对象相关的运行时上下文。参数描述导致退出上下文的异常。如果上下文没有异常退出,则所有三个参数都将为None。

     

如果提供了异常,并且该方法希望抑制异常(即,防止它被传播),则它应该返回一个真值。否则,异常将在退出此方法时正常处理。

     

请注意,__exit__()方法不应该重新引用传入的异常;这是来电者的责任。

因此,您可以看到__exit__方法将被执行,然后,默认情况下,在退出上下文管理器后,将重新引发任何异常。您可以通过创建一个简单的上下文管理器并使用异常来打破它来自行测试:

DummyContextManager(object):
    def __enter__(self):
        print('Entering...')
    def __exit__(self, exc_type, exc_value, traceback):
        print('Exiting...')  
        # If we returned True here, any exception would be suppressed!

with DummyContextManager() as foo:
    raise Exception()

当你运行这段代码时,你应该看到你想要的一切(因为print往往会在追溯过程中结束,可能会出现故障):

Entering...
Exiting...
Traceback (most recent call last):
  File "C:\foo.py", line 8, in <module>
    raise Exception()
Exception

答案 1 :(得分:1)

从上面的答案中,我对使用@contextlib.contextmanager时的最佳实践还不太清楚。我在link的评论中紧随@BenUsman

如果您正在编写上下文管理器,则必须将yield包装在try-finally块中:

from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwds):
    # Code to acquire resource, e.g.:
    resource = acquire_resource(*args, **kwds)
    try:
        yield resource
    finally:
        # Code to release resource, e.g.:
        release_resource(resource)

>>> with managed_resource(timeout=3600) as resource:
...     # Resource is released at the end of this block,
...     # even if code in the block raises an exception