一旦你进入finally块,如何确定是否引发了异常?

wim*_*wim 40 python logging exception-handling flow-control try-finally

一旦你进入finally条款,是否有可能判断是否有异常?就像是:

try:
    funky code
finally:
    if ???:
        print('the funky code raised')
Run Code Online (Sandbox Code Playgroud)

我想要做更像这样的事情干:

try:
    funky code
except HandleThis:
    # handle it
    raised = True
except DontHandleThis:
    raised = True
    raise
else:
    raised = False
finally:
    logger.info('funky code raised %s', raised)
Run Code Online (Sandbox Code Playgroud)

我不喜欢它需要捕获一个你不打算处理的异常,只是为了设置一个标志.


由于一些评论要求MCVE中的"M"较少,因此这里有一些关于用例的更多背景知识.实际问题是关于日志记录级别的升级.

  • 时髦的代码是第三方,无法更改.
  • 失败异常和堆栈跟踪不包含任何有用的诊断信息,因此logger.exception在except块中使用在此处没有帮助.
  • 如果引发了时髦的代码,那么我需要查看的一些信息已经被记录在DEBUG级别.我们没有也无法处理错误,但是想要升级DEBUG日志记录,因为所需的信息就在那里.
  • 大多数时候,时髦的代码不会引发.我不想升级一般情况下的日志记录级别,因为它太冗长了.

因此,代码在日志捕获上下文(设置自定义处理程序以拦截日志记录)下运行,并且一些调试信息会被追溯重新记录:

try:
    with LogCapture() as log:
        funky_code()  # <-- third party badness
finally:
    mylog = mylogger.WARNING if <there was exception> else mylogger.DEBUG
    for record in log.captured:
        mylog(record.msg, record.args)
Run Code Online (Sandbox Code Playgroud)

MSe*_*ert 40

使用上下文管理器

您可以使用自定义上下文管理器,例如:

class DidWeRaise:
    __slots__ = ('exception_happened', )  # instances will take less memory

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # If no exception happened the `exc_type` is None
        self.exception_happened = exc_type is not None
Run Code Online (Sandbox Code Playgroud)

然后在里面使用try:

try:
    with DidWeRaise() as error_state:
        # funky code
finally:
    if error_state.exception_happened:
        print('the funky code raised')
Run Code Online (Sandbox Code Playgroud)

它仍然是一个额外的变量,但如果你想在多个地方使用它,它可能更容易重用.而且你不需要自己切换它.

使用变量

如果您不想要上下文管理器,我会反转触发器的逻辑并没有发生异常的情况下切换它.这样,您就不需要一个except您不想处理的异常案例.最合适的地方else是在try没有抛出异常的情况下输入的子句:

exception_happened = True
try:
    # funky code
except HandleThis:
    # handle this kind of exception
else:
    exception_happened = False
finally:
    if exception_happened:
        print('the funky code raised')
Run Code Online (Sandbox Code Playgroud)

正如已经指出的那样,您可以使用所需的日志记录功能替换它(在本例中),而不是使用"切换"变量:

mylog = mylogger.WARNING
try:
    with LogCapture() as log:
        funky_code()
except HandleThis:
    # handle this kind of exception
else:
    # In case absolutely no exception was thrown in the try we can log on debug level
    mylog = mylogger.DEBUG
finally:
    for record in log.captured:
        mylog(record.msg, record.args)
Run Code Online (Sandbox Code Playgroud)

当然,如果你把它放在你的最后try(如这里建议的其他答案),它也会有用,但我更喜欢这个else条款,因为它有更多含义("只有在try块中没有异常时才会执行该代码")从长远来看可能更容易维护.虽然它比上下文管理器更需要维护,因为变量在不同的地方设置和切换.

使用sys.exc_info(仅适用于未处理的异常)

我想提到的最后一种方法可能对你没用,但对于那些只想知道是否存在未处理的异常(在任何except块中捕获或已在块内引发的异常)的未来读者可能有用except.在这种情况下,您可以使用sys.exc_info:

import sys

try:
    # funky code
except HandleThis:
    pass
finally:
    if sys.exc_info()[0] is not None:
        # only entered if there's an *unhandled* exception, e.g. NOT a HandleThis exception
        print('funky code raised')
Run Code Online (Sandbox Code Playgroud)

  • 如果最干净的方法不重写代码以便finally部分不需要知道是否引发了异常,我会感到非常惊讶.不过,这是紧随其后的第二步.来自我的+1. (8认同)

Jea*_*one 31

raised = True
try:
    funky code
    raised = False
except HandleThis:
    # handle it
finally:
    logger.info('funky code raised %s', raised)
Run Code Online (Sandbox Code Playgroud)

鉴于在选择日志级别的问题中添加了额外的背景信息,这似乎很容易适应预期的用例:

mylog = WARNING
try:
    funky code
    mylog = DEBUG
except HandleThis:
    # handle it
finally:
    mylog(...)
Run Code Online (Sandbox Code Playgroud)

  • 我同意这一点,但我也发现避免否定的形式通常也更容易理解."提出"很可能是一种积极的形式,可能会避免否定.如果不知道什么会进入真正的`finally`区块,就没有办法知道.对于仅记录值的示例代码,我根本不认为它是可读性问题. (5认同)
  • 从概念上讲,此代码不会测试是否引发了异常,而是代码块是否运行完成.因此,我也更喜欢"成功"或"完成"变量. (3认同)