在finally块中,如何确定是否引发异常?

2024-05-29 04:01:16 发布

您现在位置:Python中文网/ 问答频道 /正文

一旦进入finally子句,是否可以判断是否存在异常?类似于:

try:
    funky code
finally:
    if ???:
        print('the funky code raised')

我想让这样的东西更干些:

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)

我不喜欢它需要捕获一个异常,您不打算处理它,只需要设置一个标志。


由于有些comments在MCVE中要求更少的“M”,这里有更多关于用例的背景知识。实际的问题是关于日志级别的提升。

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

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

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)

Tags: 代码信息trueif记录codeloggerrecord
3条回答

好吧,听起来你实际上只是想修改现有的上下文管理器,或者使用类似的方法:logbook实际上有一个名为^{}的东西,可以完全按照你的要求来做。但你可以自己做,比如:

@contextmanager
def LogCapture():
    # your existing buffer code here
    level = logging.WARN
    try:
        yield
    except UselessException:
        level = logging.DEBUG
        raise  # Or don't, if you just want it to go away
    finally:
        # emit logs here

原始响应

你在考虑这个问题。

你想处理这个异常-你通过设置一个标志来处理它。也许你不关心其他事情(这看起来是个坏主意),但是如果你关心在引发异常时做些什么,那么你就需要明确它。

您正在设置一个变量,但您希望异常继续,这意味着您真正希望的是从引发的异常引发您自己的特定异常:

class MyPkgException(Exception):  pass
class MyError(PyPkgException): pass  # If there's another exception type, you can also inherit from that

def do_the_badness():
    try:
        raise FileNotFoundError('Or some other code that raises an error')
    except FileNotFoundError as e:
        raise MyError('File was not found, doh!') from e
    finally:
        do_some_cleanup()

try:
    do_the_badness()
except MyError as e:
    print('The error? Yeah, it happened')

这解决了:

  • 显式处理要处理的异常
  • 使堆栈跟踪和原始异常可用
  • 允许将要处理原始异常的代码在其他地方处理引发的异常
  • 允许一些顶级异常处理代码只捕获MyPkgException来捕获所有异常,以便它可以记录某些内容并以良好的状态退出,而不是以难看的堆栈跟踪
raised = True
try:
    funky code
    raised = False
except HandleThis:
    # handle it
finally:
    logger.info('funky code raised %s', raised)

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

mylog = WARNING
try:
    funky code
    mylog = DEBUG
except HandleThis:
    # handle it
finally:
    mylog(...)

使用contextmanager

您可以使用自定义contextmanager,例如:

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

然后在try中使用它:

try:
    with DidWeRaise() as error_state:
        # funky code
finally:
    if error_state.exception_happened:
        print('the funky code raised')

它仍然是一个附加变量,但是如果您想在多个地方使用它,那么重用它可能要容易得多。你不需要自己去切换。

使用变量

如果您不需要contextmanager,我将反转触发器的逻辑,并仅在发生异常时切换它。这样,对于不想处理的异常,就不需要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')

正如已经指出的,您可以用所需的日志功能替换它(在本例中),而不是使用“toggle”变量:

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)

当然,如果把它放在try的末尾(正如这里的其他答案所建议的那样),它也会起作用,但是我更喜欢else子句,因为它有更多的含义(“只有在try块中没有异常的情况下,代码才应该被执行”,并且从长远来看可能更容易维护。尽管它仍然比上下文管理器更需要维护,因为变量是在不同的地方设置和切换的。

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

我想提到的最后一种方法可能对您不太有用,但可能对未来的读者有用,他们只想知道是否存在未处理的异常(在任何except块中捕获的或在except块中引发的异常)。在这种情况下,可以使用^{}

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')

相关问题 更多 >

    热门问题