一旦你进入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 块,最后引发异常。