简介
在日常开发中,我们的大部分时间都会花在阅读traceback模块信息以及调试代码上。本文我们将改进traceback模块,让其中的提示信息更加简洁准确。
基于这一目的,我们将会自定义exception hooks(异常处理钩子),用来去除traceback中的冗余信息,只留下解决报错所需的内容。此外,我还会介绍一些好用的第三方库,你可以直接使用其中的exception hooks,来简化traceback模块。
exception hooks
假如程序的异常信息没有被try/catch捕获到,python解释器就会调用sys.excepthook()函数,它会接收3个参数,分别是:type, value, traceback。这个函数也被称为exception hook,会输出程序的异常信息。
我们来看看下面这个例子:
import sys
def exception_hook(exc_type, exc_value, tb):
print('traceback:')
filename = tb.tb_frame.f_code.co_filename
name = tb.tb_frame.f_code.co_name
line_no = tb.tb_lineno
print(ffile {filename} line {line_no}, in {name})
# exception type 和 value
print(f{exc_type.__name__}, message: {exc_value})
sys.excepthook = exception_hook
在这个例子中,我们可以从traceback (tb)对象中获取到异常信息出现的位置,位置信息包括:文件名(f_code.co_filename),函数/模块名(f_code.co_name), 和行数(tb_lineno)。此外,我们可以使用exc_type和exc_value变量来获取异常信息的内容。
当我们调用一个会产生错误的函数时,exception_hook会输出如下内容:
def do_stuff():
# 写一段会产生异常的代码
raise valueerror(some error message)
do_stuff()
# traceback:
# file /home/some/path/exception_hooks.py line 22, in
# valueerror, message: some error message
上述例子提供了一部分异常信息,但要想获取调试代码所需的全部信息,并知道异常出现的时间及位置,我们还需要深入研究下traceback对象:
def exception_hook(exc_type, exc_value, tb):
local_vars = {}
while tb:
filename = tb.tb_frame.f_code.co_filename
name = tb.tb_frame.f_code.co_name
line_no = tb.tb_lineno
print(ffile {filename} line {line_no}, in {name})
local_vars = tb.tb_frame.f_locals
tb = tb.tb_next
print(flocal variables in top frame: {local_vars})
...
# file /home/some/path/exception_hooks.py line 41, in
# file /home/some/path/exception_hooks.py line 7, in do_stuff
# local variables in top frame: {'some_var': 'data'}
由上面的例子可以看出,traceback对象(tb)本质上是一个链表 - 存储着所有出现的exceptions。因此可以使用tb_next对tb进行遍历,并打印每一个异常的信息。在此基础上,还可以使用tb_frame.f_locals属性将变量输出到console中,这有助于调试代码。
使用traceback对象输出异常信息是可行的,但是比较麻烦,此外输出的信息可读性较差。更方便的做法是使用traceback模块,该模块内置了许多提取异常信息的辅助函数。
目前我们已经介绍了exception hooks的基础知识,接下来我们可以自定义一个exception hooks,并加入一些实用的特性。
自定义exception hooks
我们还可以让异常信息自动存入文件中,在之后调试代码的时候查看:log_file_path = ./some.log
file = open(log_file_path, mode=w)
def exception_hook(exc_type, exc_value, tb):
file.write(*** exception: ***n)
traceback.print_exc(file=file)
file.write(n*** traceback: ***n)
traceback.print_tb(tb, file=file)
# *** exception: ***
# nonetype: none
#
# *** traceback: ***
# file /home/some/path/exception_hooks.py, line 82, in
# do_stuff()
# file /home/some/path/exception_hooks.py, line 7, in do_stuff
# raise valueerror(some error message)
异常信息默认会存储到stderr中,如果你想改变存储位置,可以这样做:import logging
logging.basicconfig(
level=logging.critical,
format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s',
datefmt='%h:%m:%s',
stream=sys.stdout
)
def exception_hook(exc_type, exc_value, exc_traceback):
logging.critical(uncaught exception:, exc_info=(exc_type, exc_value, exc_traceback))
# [17:28:33] {/home/some/path/exception_hooks.py:117} critical - uncaught exception:
# traceback (most recent call last):
# file /home/some/path/exception_hooks.py, line 122, in
# do_stuff()
# file /home/some/path/exception_hooks.py, line 7, in do_stuff
# raise valueerror(some error message)
# valueerror: some error message
我们还可以给提示信息的某一部分设置颜色:# pip install colorama
from colorama import init, fore
init(autoreset=true)# reset the color after every print
def exception_hook(exc_type, exc_value, tb):
local_vars = {}
while tb:
filename = tb.tb_frame.f_code.co_filename
name = tb.tb_frame.f_code.co_name
line_no = tb.tb_lineno
# prepend desired color (e.g. red) to line
print(f{fore.red}file {filename} line {line_no}, in {name})
local_vars = tb.tb_frame.f_locals
tb = tb.tb_next
print(f{fore.green}local variables in top frame: {local_vars})
除了上面介绍的例子,你还可以输出每一帧的局部变量,或者找到出现异常的行中所引用的变量。这些exception hooks已经很成熟了,相比于自定义exception hooks,我建议你阅读下其他开发者的源码,学习下他们的设计思路。
输出每一帧的局部变量[1] 找到出现异常的行中所引用的变量[2]
第三方库中的exception hooks
自定义一个exception hook很有趣,但许多第三方库已经实现这一功能了。与其自己造轮子,不如看看其他优秀的工具。
首先,我个人最喜欢的是rich,可以直接用pip进行安装,随后导入使用。如果你只想在一个例子中使用,可以这样做:python -m rich.traceback# https://rich.readthedocs.io/en/latest/traceback.html
# pip install rich
# python -m rich.traceback
from rich.traceback import install
install(show_locals=true)
do_stuff()# raises valueerror
better_exceptions也很受欢迎,我们需要先设置环境变量better_exceptions=1,再用pip安装。此外,如果你的term变量不是xterm,还要把supports_color设置为true。# https://github.com/qix-/better-exceptions
# pip install better_exceptions
# export better_exceptions=1
import better_exceptions
better_exceptions.max_length = none
# 检查你的 term 变量是否被设置为 `xterm`, 如果没有执行以下操作
# see issue: https://github.com/qix-/better-exceptions/issues/8
better_exceptions.supports_color = true
better_exceptions.hook()
do_stuff()# raises valueerror
使用最方便的库是pretty_errors,只需导入即可:# https://github.com/onelivesleft/prettyerrors/
# pip install pretty_errors
import pretty_errors
# 如果你对默认配置满意的话,则无需修改
pretty_errors.configure(
filename_display= pretty_errors.filename_extended,
line_number_first = true,
display_link= true,
line_color= pretty_errors.red + '> ' + pretty_errors.default_config.line_color,
code_color= '' + pretty_errors.default_config.line_color,
truncate_code = true,
display_locals= true
)
do_stuff()
除了直接导入外,上面的代码还显示了该库的一些可选配置。更多的配置可以查看这里:配置[3]
ipython的ultratb模块# https://ipython.readthedocs.io/en/stable/api/generated/ipython.core.ultratb.html
# pip install ipython
import ipython.core.ultratb
# also colortb, formattedtb, listtb, syntaxtb
sys.excepthook = ipython.core.ultratb.verbosetb(color_scheme='linux')# other colors: nocolor, lightbg, neutral
do_stuff()
stackprinter库# https://github.com/cknd/stackprinter
# pip install stackprinter
import stackprinter
stackprinter.set_excepthook(style='darkbg2')
do_stuff()
结论
本文我们学习了如何自定义exception hooks,但我更推荐使用第三方库。你可以在本文介绍的第三方库中任选一个喜欢的,用到项目中。需要注意的是使用自定义exception hooks可能会丢失某些关键信息,例如:本文中的某些例子中,输出中缺少文件路径,在远程调试代码这无疑很不方便,因此,需要谨慎使用。
以上就是涨知识!python 的异常信息还能这样展现的详细内容。