How to determine if an exception was raised once you're in the finally block?
一旦进入
1 2 3 4 5 | try: funky code finally: if ???: print('the funky code raised') |
我想要做更像这样的事情干:
1 2 3 4 5 6 7 8 9 10 11 12 | 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) |
我不喜欢它需要捕获一个你不打算处理的异常,只是为了设置一个标志。
由于一些评论要求MCVE中的"M"较少,因此这里有一些关于用例的更多背景知识。实际问题是关于日志记录级别的升级。
- 时髦的代码是第三方,无法更改。
-
故障异常和堆栈跟踪不包含任何有用的诊断信息,因此在except块中使用
logger.exception 在此处没有帮助。 - 如果引发了时髦的代码,那么我需要查看的一些信息已经被记录在DEBUG级别。我们没有也无法处理错误,但是想要升级DEBUG日志记录,因为所需的信息就在那里。
- 大多数时候,时髦的代码不会引发。我不想升级一般情况下的日志记录级别,因为它太冗长了。
因此,代码在日志捕获上下文(设置自定义处理程序以拦截日志记录)下运行,并且一些调试信息会被追溯重新记录:
1 2 3 4 5 6 7 | 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) |
使用上下文管理器
您可以使用自定义上下文管理器,例如:
1 2 3 4 5 6 7 8 9 | 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 |
然后在
1 2 3 4 5 6 | try: with DidWeRaise() as error_state: # funky code finally: if error_state.exception_happened: print('the funky code raised') |
它仍然是一个额外的变量,但如果你想在多个地方使用它,它可能更容易重用。而且你不需要自己切换它。
使用变量
如果您不想要上下文管理器,我会反转触发器的逻辑并仅在没有发生异常的情况下切换它。这样,您不需要
1 2 3 4 5 6 7 8 9 10 | 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') |
正如已经指出的那样,您可以使用所需的日志记录功能替换它(在本例中),而不是使用"切换"变量:
1 2 3 4 5 6 7 8 9 10 11 12 | 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) |
当然,如果你把它放在你的
使用
我想提到的最后一种方法可能对你没用,但对于那些只想知道是否存在未处理的异常(未在任何
1 2 3 4 5 6 7 8 9 10 | 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') |
1 2 3 4 5 6 7 8 | raised = True try: funky code raised = False except HandleThis: # handle it finally: logger.info('funky code raised %s', raised) |
鉴于在选择日志级别的问题中添加了额外的背景信息,这似乎很容易适应预期的用例:
1 2 3 4 5 6 7 8 | mylog = WARNING try: funky code mylog = DEBUG except HandleThis: # handle it finally: mylog(...) |
好吧,所以听起来你实际上只想修改现有的上下文管理器,或者使用类似的方法:
1 2 3 4 5 6 7 8 9 10 11 | @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 |
原始回应
你在想这个有点横向。
您确实打算处理异常 - 您通过设置标志来处理它。也许你不关心任何其他事情(这似乎是一个坏主意),但如果你关心在引发异常时做某事,那么你想要明确它。
您正在设置变量,但希望异常继续的事实意味着您真正想要的是从引发的异常中引发您自己的特定异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 来捕获所有异常,这样它就可以记录一些东西并以一个漂亮的状态而不是一个丑陋的堆栈跟踪退出
您可以轻松地将捕获的异常分配给变量并在finally块中使用它,例如:
1 2 3 4 5 6 7 8 9 10 11 | >>> x = 1 >>> error = None >>> try: ... x.foo() ... except Exception as e: ... error = e ... finally: ... if error is not None: ... print(error) ... 'int' object has no attribute 'foo' |
如果是我,我会对您的代码进行一些重新排序。
1 2 3 4 5 6 7 8 9 10 11 12 | raised = False try: # funky code except HandleThis: # handle it raised = True except Exception as ex: # Don't Handle This raise ex finally: if raised: logger.info('funky code was raised') |
我已将引发的布尔赋值放在try语句之外以确保范围,并使最终的except语句成为您不想处理的异常的一般异常处理程序。
此样式确定您的代码是否失败。另一种方法可能是我确定代码何时成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | success = False try: # funky code success = True except HandleThis: # handle it pass except Exception as ex: # Don't Handle This raise ex finally: if success: logger.info('funky code was successful') else: logger.info('funky code was raised') |