Python实现自定义异常堆栈信息的示例代码
作者:古明地觉 发布时间:2021-10-29 14:27:47
当我们的程序报错时,解释器会将整个异常的堆栈信息全部输出出来,举个例子:
def foo():
raise RuntimeError("抛一个异常")
def bar():
foo()
def main():
bar()
main()
如果执行这段代码,会得到以下报错信息:
解释器会将异常产生的整个调用链都给打印出来,那么问题来了,我们能不能自定义这些报错信息呢?
答案是可以的,我们只要拿到这些报错信息,然后再进行修改即可。那么如何才能拿到呢?显然需要借助于 traceback 对象。
import sys
def foo():
raise RuntimeError("抛一个异常")
def bar():
foo()
def main():
bar()
try:
main()
except Exception:
# sys.exc_info() 返回一个元组,里面有三个元素
# 分别是:异常类型、异常值、异常的堆栈信息
exc_type, exc_val, exc_tb = sys.exc_info()
print(exc_type)
print(exc_val, type(exc_val))
print(exc_tb)
"""
<class 'RuntimeError'>
抛一个异常 <class 'RuntimeError'>
<traceback object at 0x7fe35811de40>
"""
每一个函数在运行时都会创建一个栈帧,栈帧在 CPython 里面由 PyFrameObject 结构体表示。同时每个栈帧都会对应一个 PyTracebackObject,也就是异常堆栈,即代码中的 exc_tb。
上面的代码在执行函数 foo 的时候出现了异常,那么解释器会创建对应的 traceback 对象,由于 foo 内部没有异常捕获,因此会回退到上一级栈帧,继续寻找异常捕获逻辑。而 foo 的上一级栈帧是 bar 的栈帧,因此解释器同样会基于 bar 的栈帧创建 traceback 对象,并且 bar 的 traceback 对象的 tb_next 指向 foo 的 traceback 对象。
就这样栈帧一层层的回退,整个过程我们称之为栈帧展开。在栈帧展开的过程中,解释器不断地创建与各个栈帧对应的 traceback,并将其链接成链表。
如果回退到最外层(模块)也没有找到异常捕获逻辑,那么解释器就要输出异常信息了,从模块对应的 traceback 开始不断遍历,将每一层信息都输出出来,就是我们看到的样子。
而我们也可以在拿到 traceback 之后,手动输出出来。
import sys
import traceback
def foo():
raise RuntimeError("抛一个异常")
def bar():
foo()
def main():
bar()
try:
main()
except Exception:
exc_type, exc_val, exc_tb = sys.exc_info()
error_msg = "".join(
traceback.format_exception(
exc_type, exc_val, exc_tb)
)
print(error_msg)
"""
Traceback (most recent call last):
File "/Users/satori/Desktop/project/main.py", line 14, in <module>
main()
File "/Users/satori/Desktop/project/main.py", line 11, in main
bar()
File "/Users/satori/Desktop/project/main.py", line 8, in bar
foo()
File "/Users/satori/Desktop/project/main.py", line 5, in foo
raise RuntimeError("抛一个异常")
RuntimeError: 抛一个异常
"""
Python 有一个标准模块也叫 traceback,使用它的 format_exception 函数,我们可以拿到格式化后的异常堆栈信息,而该函数接收的参数正是 exc_type, exc_val, exc_tb。
虽然我们拿到了异常的堆栈信息,但是还不够,因为这里是通过手动捕获异常的方式。而在生产上,很多时候我们并不知道哪里会抛出异常,所以我们需要在不使用异常捕获逻辑的前提下,自动捕获异常。是不是有点绕了呢?我们举个例子。
import sys
def catch(exc_type, exc_val, exc_tb):
"""
光看名字,就应该知道这三个参数的含义了
"""
print("报错啦")
print(exc_val)
# 之后当出现异常的时候,如果没有异常捕获逻辑
# 就会执行 catch 函数
sys.excepthook = catch
raise ZeroDivisionError("除零错误")
"""
报错啦
除零错误
"""
代码中我们 raise 了一个异常,默认情况下解释器应该将异常输出到 stderr 当中,然后中止运行。但是我们替换了 sys.excepthook,那么在出现异常的时候,解释器会去执行我们这里替换之后的 catch 函数,并自动将 exc_type, exc_val, exc_tb 作为参数传进去。
而一旦 cache 函数执行完毕,程序就结束了。
那么接下来我们就可以对异常输出进行改造了,至于怎么改,完全由你来决定,我们这里给个示例。
import sys
from io import StringIO
import re
import traceback
from rich import print
def catch(exc_type, exc_val, exc_tb):
buf = StringIO()
# 得到一个列表
errors = traceback.format_exception(
exc_type, exc_val, exc_tb)
buf.write(f"[blue]程序出现异常啦, 客官请看下面:\n\n")
# 遍历
i = 1
while i < len(errors):
match = re.search(r'File "(.+?)", line (\d+), in (.+)\s*(.*)',
errors[i].strip())
if match is not None:
file_path, lineno, where, reason = match.groups()
buf.write(
f"在文件 [red bold]{file_path}[/red bold] "
f"[green bold]{where}[/green bold] 的"
f"第 [yellow bold]{lineno}[/yellow bold] 行\n"
)
buf.write(f"执行了 {reason.strip()}\n\n")
i += 1
buf.write(f"[cyan bold]{errors[-1].strip()}")
print(buf.getvalue())
sys.excepthook = catch
def foo():
# raise a exception
{} + ()
def bar():
foo()
def main():
bar()
main()
我们执行这段代码,看看它的错误输出是什么样子。
怎么样,是不是很好玩呢?只要把 sys.excepthook = cache 这段逻辑加载到程序中,我们就可以自定义异常显示信息了。
当然啦,根据异常的不同,解释器可能输出更复杂的信息,所以我们上面的代码并不完善,但你可以根据实际情况进行修改。只要知道如何获取解释器输出的异常信息,以及 sys.excepthook 的用法就足够了。
最后再强调一下,解释器在发现异常的时候,会立即调用 sys.excepthook。而一旦调用结束,整个程序就结束了。
来源:https://mp.weixin.qq.com/s/9cF3K23xKYSH1A2HaH_7bQ


猜你喜欢
- javascript/js的ajax的GET请求:<script type="text/javascript"&g
- 本文实例讲述了Python 面向对象之类class和对象基本用法。分享给大家供大家参考,具体如下:类(class):定义一件事物的抽象特点,
- 由于 Ubuntu 中的汉字输入实在是太不友好了,所以装了个 搜狗输入法,好不容易把 搜狗输入法装好,本以为可以开开心心的搞代码了,然而。。
- 本文介绍了SpringBoot 中使用JSP的方法示例,分享给大家,具体如下:依赖: <parent>
- CSS浮动一直是个比较让人郁闷的问题,很多的布局问题都出在浮动上,特别是当浮动的列数很多时,但其实只要理解了两列结构的浮动,面对多列数的浮动
- 1、使用函数模型API,新建一个model,将输入和输出定义为原来的model的输入和想要的那一层的输出,然后重新进行predict.#co
- pyinstaller打包问题简单介绍一下pyinstaller常用的参数:可选参数示例说明-Fpyinstaller -F demo.py
- 蜗牛很慢。蜗牛快递会怎样?答案是:当然也会很慢。但是蜗牛尽了他的全力,为了它的兔子朋友,以生命在奔跑。每天都是24个小时,快的只是速度,却不
- Python中的缩进(Indentation)决定了代码的作用域范围。这一点和传统的c/c++有很大的不同(传统的c/c++使用花括号{}符
- vue在做大型项目时,会用到多状态管理,vuex允许我们将store分割成多个模块,每个模块内都有自己的state、mutation、act
- 登录百度AL开发平台在控制台选择语音合成创建应用填写应用信息在应用列表获取(Appid、API Key、Secret Key)6. 安装py
- vue中引入html静态页面功能:系统中需增加帮助中心页面,由于页面较长,需要实现锚点定位跳转。1、开始用的路由方式,首先在router文件
- 环境:【wind2003[open Tftp server] + virtualbox:ubuntn10 server】tftp
- 对于单页应用,官方提供了vue-router进行路由跳转的处理,本篇主要也是基于其官方文档写作而成。安装基于传统,我更喜欢采用npm包的形式
- 『写在前面』以CTC Beam search decoder为例,简单整理一下TensorFlow实现自定义Op的操作流程。基本的流程1.
- 目录一、对比数据类型二、可变集合构造方法三、不可变集合的构造方法四、集合构造注意事项 Python集合又是一种新的数据类型,集合有
- 看到豆瓣上有网友提了这个问题,看到回答的人不多,忍不住写了下面的内容。工作中最常用到的统计方法有哪些?根据我自己的经验给举些例子。1.通过一
- 目录当前时间实例1:实例2:指定时间戳实例1:实例2:总结我们将会启用到time库:当前时间实例1:import time# 获得当前时间时
- Pandas函数的核心功能是,既计算了统计值,又保留了明细数据。为了更好地理解transform和agg的不同,下面从实际的应用场景出发进行
- Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型M,视图V和模版T。它最初是被开发来用于管理