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

Tho*_*ner 16 python exception-handling contextmanager

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

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

def main():
    raise Exception

with cron.lock() as lockid:
    print('Got lock: %i' % lockid)
    main()
Run Code Online (Sandbox Code Playgroud)

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

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

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

PS.该finally块是否相关?

Hen*_*ter 27

如果上下文管理器被异常中断,则该__exit__方法被调用为正常.事实上,传递给__exit__所有人的参数都与处理这种情况有关!来自文档:

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()
Run Code Online (Sandbox Code Playgroud)

当你运行这段代码时,你应该看到你想要的一切(可能是乱序的,因为它print往往会在追踪中间结束):

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

  • 注意:对于带有`@ contextlib.contextmanager`的生成器,一个[必须](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager)使用`try ... finally`语句来制作确保资源被正确释放,因为`with`block中引发的异常被`contextmanager`捕获并在*generator body内重新引发*.如果在这样的`with`块中引发异常,则不会调用`yield`语句之后的代码. (8认同)

Dan*_*ell 13

@contextlib.contextmanager从上面的答案中,我不太清楚使用时的最佳实践。我关注了@BenUsman评论中的链接

如果您正在编写上下文管理器,则必须包装yieldintry-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
Run Code Online (Sandbox Code Playgroud)