如何确定进入 finally 块后是否引发了异常?



一旦你进入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)

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


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

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

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

try:
with LogCapture() as log:
funky_code()  # <-- third party badness
finally:
# log events are buffered in memory. if there was an exception,
# emit everything that was captured at a WARNING level
for record in log.captured:
if <there was an exception>:
log_fn = mylogger.warning
else:
log_fn = getattr(mylogger, record.levelname.lower())
log_fn(record.msg, record.args)

使用上下文管理器

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

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

它仍然是一个附加变量,但如果您想在多个地方使用它,重用起来可能会容易得多。而且您无需自己切换它。

使用变量

如果您不想要上下文管理器,我会反转触发器的逻辑,仅在没有发生异常时才切换它。这样,您就不需要为不想处理的异常提供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')

正如已经指出的,您可以使用所需的日志记录函数替换它(在这种情况下),而不是使用"切换"变量:

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块中引发的异常)。在这种情况下,您可以使用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')
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(...)

您可以轻松地将捕获的异常分配给变量并在 finally 块中使用它,例如:

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

好吧,听起来你实际上只是想修改现有的上下文管理器,或者使用类似的方法:logbook实际上有一个叫做FingersCrossedHandler的东西,可以做你想要的。但你可以自己做,比如:

@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 = 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 语句之外以确保作用域,并使 final except 语句成为您不想处理的异常的通用异常处理程序。

此样式确定代码是否失败。另一种方法可能是确定您的代码何时成功。

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

如果发生异常 --> 将此逻辑放在异常块中。
如果未发生异常 --> 将此逻辑放在代码中可能发生异常的点之后的 try 块中。

最后,根据Python语言参考,块应该保留给"清理操作"。当指定 finally 时,解释器在except情况下继续如下:保存异常,然后首先执行 finally 块,最后引发异常。

相关内容

  • 没有找到相关文章

最新更新