Python异常记录Traceback模块详解

今日记录一下一个非常好用的模块:traceback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding:utf-8 -*-
def func1():
raise Exception("--func1 exception--")


def main():
try:
func1()
except Exception as e:
print(e)


if __name__ == '__main__':
main()

执行后输出如下:

1
--func1 exception--

通过示例,我们发现普通的打印异常只有很少量的信息(通常是异常的value值),这种情况下我们很难定位在哪块代码出的问题,以及如何出现这种异常。那么到底要如何打印更加详细的信息呢?下面我们就来一一介绍。

sys.exc_info和traceback object

Python程序的traceback信息均来源于一个叫做traceback object的对象,而这个traceback object通常是通过函数sys.exc_info()来获取的,先来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- coding:utf-8 -*-

import sys


def func1():
raise NameError("--func1 exception--")


def main():
try:
func1()
except Exception as e:
print(e)
exc_type, exc_value, exc_traceback_obj = sys.exc_info()
print(f"exc_type: {exc_type}")
print(f"exc_value: {exc_value}")
print(f"exc_traceback_obj: {exc_traceback_obj}")


if __name__ == '__main__':
main()

执行输出后如下:

1
2
3
4
--func1 exception--
exc_type: <class 'NameError'>
exc_value: --func1 exception--
exc_traceback_obj: <traceback object at 0x1024899c8>

通过以上示例我们可以看出,sys.exc_info()获取了当前处理的exception的相关信息,并返回一个元组,元组的第一个数据是异常的类型(示例是NameError类型),第二个返回值是异常的value值,第三个就是我们要的traceback object.

有了traceback object我们就可以通过traceback module来打印和格式化traceback的相关信息,下面我们就来看下traceback module的相关函数。

traceback module

Python的traceback module提供一整套接口用于提取,格式化和打印Python程序的stack traces信息,下面我们通过例子来详细了解下这些接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -*- coding:utf-8 -*-

import sys
import traceback


def func1():
raise NameError("--func1 exception--")


def main():
try:
func1()
except Exception as e:
exc_type, exc_value, exc_traceback_obj = sys.exc_info()
traceback.print_tb(exc_traceback_obj)


if __name__ == '__main__':
main()

输出结果:

1
2
3
4
File "test_python.py", line 13, in main
func1()
File "test_python.py", line 8, in func1
raise NameError("--func1 exception--")

这里我们可以发现打印的异常信息更加详细了,下面我们了解下print_tb的详细信息:

1
2
3
4
5
6
7
8
def print_tb(tb, limit=None, file=None):
"""Print up to 'limit' stack trace entries from the traceback 'tb'.

If 'limit' is omitted or None, all entries are printed. If 'file'
is omitted or None, the output goes to sys.stderr; otherwise
'file' should be an open file or file-like object with a write()
method.
"""
  • tb: 这个就是traceback object, 是我们通过sys.exc_info获取到的
  • limit: 这个是限制stack trace层级的,如果不设或者为None,就会打印所有层级的stack trace
  • file: 这个是设置打印的输出流的,可以为文件,也可以是stdout之类的file-like object。如果不设或为None,则输出到sys.stderr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding:utf-8 -*-

import sys
import traceback


def func1():
raise NameError("--func1 exception--")


def func2():
func1()


def main():
try:
func2()
except Exception as e:
exc_type, exc_value, exc_traceback_obj = sys.exc_info()
traceback.print_exception(exc_type, exc_value, exc_traceback_obj, limit=2, file=sys.stdout)


if __name__ == '__main__':
main()

输出结果:

1
2
3
4
5
6
Traceback (most recent call last):
File "test_python.py", line 17, in main
func2()
File "test_python.py", line 12, in func2
func1()
NameError: --func1 exception--

看下print_exception的源码:

1
2
3
4
5
6
7
8
9
10
11
def print_exception(etype, value, tb, limit=None, file=None, chain=True):
"""Print exception up to 'limit' stack trace entries from 'tb' to 'file'.

This differs from print_tb() in the following ways: (1) if
traceback is not None, it prints a header "Traceback (most recent
call last):"; (2) it prints the exception type and value after the
stack trace; (3) if type is SyntaxError and value has the
appropriate format, it prints the line where the syntax error
occurred with a caret on the next line indicating the approximate
position of the error.
"""
  • print_tb相比多了两个参数etypevalue,分别是exception typeexception value,加上tb(traceback object),正好是sys.exc_info()返回的三个值
  • 另外,与print_tb相比,打印信息多了开头的”Traceback (most…)”信息以及最后一行的异常类型和value信息
  • 还有一个不同是当异常为SyntaxError时,会有”^”来指示语法错误的位置

print_exc是简化版的print_exception, 由于exception type, valuetraceback object都可以通过sys.exc_info()获取,因此print_exc()就自动执行exc_info()来帮助获取这三个参数了,也因此这个函数是我们的程序中最常用的,因为它足够简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding:utf-8 -*-

import sys
import traceback


def func1():
raise NameError("--func1 exception--")


def func2():
func1()


def main():
try:
func2()
except Exception as e:
traceback.print_exc(limit=1, file=sys.stdout)


if __name__ == '__main__':
main()

输出结果:

1
2
3
4
Traceback (most recent call last):
File "test_python.py", line 17, in main
func2()
NameError: --func1 exception--

(由于limit=1,因此只有一个层级被打印出来)

print_exc的源码:

1
2
3
def print_exc(limit=None, file=None, chain=True):
"""Shorthand for 'print_exception(*sys.exc_info(), limit, file)'."""
print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain)
  • 函数内部直接调用了peint_exception

format_exc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# -*- coding:utf-8 -*-

import logging
import sys
import traceback

logger = logging.getLogger("traceback_test")


def func1():
raise NameError("--func1 exception--")


def func2():
func1()


def main():
try:
func2()
except Exception as e:
logger.error(traceback.format_exc(limit=None))


if __name__ == '__main__':
main()

从这个例子可以看出有时候我们想得到的是一个字符串,比如我们想通过logger将异常记录在log里,这个时候就需要format_exc了,这个也是最常用的一个函数,它跟print_exc用法大致相同,只是不直接打印而是返回了字符串。

format_exc的源码:

1
2
3
def format_exc(limit=None, chain=True):
"""Like print_exc() but return a string."""
return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain))
  • format_exc参数不支持输出到文件或者标准输出 只会返回一个字符串

traceback module中还有一些其它的函数,但因为并不常用,就不在展开来讲,感兴趣的同学可以看下参考链接中的文档。

推荐使用loguru记录异常信息 这个库能够更简洁的方式记录一些栈信息

参考文章:

Python Traceback详解

知识就是财富
如果您觉得文章对您有帮助, 欢迎请我喝杯水!