使用 Python 捕获每一个异常
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
说到异常处理,我们都知道,棘手的情况可以通过三思而后行来解决,而对于其他情况,简单的斜杠“ try/”except就能搞定。但是,如果你错过了异常会发生什么呢?如果你使用的是任何现代面向对象编程语言,那么你的程序很可能会在关闭前立即向标准错误输出(STDERR)打印一些回溯数据。仅此而已。
在生产环境中,这是不可接受的。失败绝不是一个选项。
那么,如果我告诉你还有另一种方法呢?如果你可以捕获一个你从未明确尝试捕获的异常,并在程序崩溃之前优雅地处理它呢?
让我来介绍sys一下excepthook!
sys.excepthook(type, value, tb)
解释器会在程序因未处理的异常而崩溃之前立即调用此函数。
那么,我们可以用这个做什么呢?我们可以创建一个接受相同参数的函数,并将其赋值给它,sys.excepthook这样对原函数的调用sys.excepthook实际上就是对我们自己函数的调用。
例子:
import sys
def my_exception_hook(type, value, tb):
"""
Intended to be assigned to sys.exception as a hook.
Gives programmer opportunity to do something useful with info from uncaught exceptions.
Parameters
type: Exception type
value: Exception's value
tb: Exception's traceback
"""
error_msg = "An exception has been raised outside of a try/except!!!\n" \
f"Type: {type}\n" \
f"Value: {value}\n" \
f"Traceback: {tb}"
# We're just printing the error out for this example,
# but you should do something useful here!
# Log it! Email your boss! Tell your cat!
print(error_msg)
sys.excepthook = my_exception_hook
# Let's do something silly now
x = 10 + "10"
我把这段代码保存到了一个名为“testfile.py”的文件中。我们运行一下看看会得到什么结果。
josh@debian:~/tmp$ python3 testfile.py
An exception has been raised outside of a try/except!!!
Type: <class 'TypeError'>
Value: unsupported operand type(s) for +: 'int' and 'str'
Traceback: <traceback object at 0x7fac25f231c8>
这太好了。我们掌握了一些关于异常的详细信息,可以记录下来并/或提醒开发人员。我们不仅知道在没有 try/except 语句的情况下抛出了异常,而且还知道这是一个 TypeError,原因是+使用了 `Traceback` 函数来执行加法/字符串连接操作(取决于开发人员的意图)。话虽如此,如果能了解更多关于这个错误的信息就更好了,我们可以利用从回溯参数中提取的信息来了解更多信息tb。
从回溯信息中提取详细信息
为了从回溯信息中提取信息,我们需要导入 Python 的 `getTraceback`traceback模块。导入完成后,我们将使用extract_tb()该模块的函数从回溯信息中提取信息。本文仅关注如何传递extract_tb()回溯信息,但您需要知道,可以使用 `--returns` 参数来限制返回的信息量limit。
extract_tb()返回一个实例,traceback.StackSummary其中包含有关回溯的所有信息。幸运的是,此类有一个format()方法可以返回一个字符串列表,每个字符串代表堆栈中的一个帧。
现在,让我们重写上面的函数,通过从回溯对象(我们的tb参数)中提取详细信息,来获取有关异常的更多信息。
import sys
import traceback
def my_exception_hook(type, value, tb):
"""
Intended to be assigned to sys.exception as a hook.
Gives programmer opportunity to do something useful with info from uncaught exceptions.
Parameters
type: Exception type
value: Exception's value
tb: Exception's traceback
"""
# NOTE: because format() is returning a list of string,
# I'm going to join them into a single string, separating each with a new line
traceback_details = '\n'.join(traceback.extract_tb(tb).format())
error_msg = "An exception has been raised outside of a try/except!!!\n" \
f"Type: {type}\n" \
f"Value: {value}\n" \
f"Traceback: {traceback_details}"
print(error_msg)
sys.excepthook = my_exception_hook
# Let's do something silly now
x = 10 + "10"
让我们运行一下,看看结果如何!
josh@debian:~/tmp$ python3 testfile.py
An exception has been raised outside of a try/except!!!
Type: <class 'TypeError'>
Value: unsupported operand type(s) for +: 'int' and 'str'
Traceback: File "testfile.py", line 19, in <module>
x = 10 + "10"
太好了!我们可以看到发生异常的文件、发生异常的代码行,以及导致异常的错误逻辑!
我在这里并没有对异常数据进行太多处理,这只是一个例子。我建议您将此异常记录到某个地方。此外,请考虑为这类异常设置一个特殊的退出代码。
总而言之……
请不要误解我的意思——我并不是建议你用这种方法替换你现有的try/except代码块。这种方法只是我作为紧急备用方案的手段。如果我的代码最终出现在这里,我仍然认为是一种损失,但至少我可以通过这种方式了解问题所在,而不是让异常详情被任何监视程序(如果有的话)获取STDERR。