为何Python linecache模块影响traceback模块却不影响常规回溯?
从你的测试代码和输出结果能明显看到差异:用traceback.print_exc()输出的回溯能显示出错的1/0代码行,但解释器默认抛出未捕获异常时的回溯却做不到。下面分两部分解答你的疑问:
一、常规回溯不使用linecache的原因
Python解释器默认的未捕获异常回溯逻辑,并没有依赖linecache模块来获取源代码行。它的底层实现是直接尝试从磁盘上的物理文件读取对应行的内容。
而你定义的test()函数是编译在虚拟文件名<test>下的,这个文件并不存在于磁盘中,所以默认回溯无法找到对应的源代码,自然就不会显示那行出错的代码。
反观traceback模块的print_exc()方法,它在获取源代码行时会主动调用linecache.getline()来查找内容——你已经提前把<test>的代码缓存到了linecache.cache里,所以它能顺利拿到1/0这行代码并展示出来。
二、让常规回溯显示动态代码的方法
要实现这个需求,我们可以替换Python的全局异常处理钩子sys.excepthook,让默认的异常输出逻辑用上linecache的缓存。具体做法如下:
自定义sys.excepthook
重写sys.excepthook,让它使用traceback模块的打印逻辑,这样就能让默认异常回溯也读取linecache中的内容:
import sys import traceback import linecache code = """ def test(): 1/0 """ filename = "<test>" c = compile(code, filename, 'exec') exec(c) # 将动态代码加入linecache缓存 linecache.cache[filename] = (len(code), None, code.splitlines(keepends=True), filename) # 自定义异常处理钩子 def custom_excepthook(exc_type, exc_value, exc_traceback): # 调用traceback模块的打印方法,自动读取linecache traceback.print_exception(exc_type, exc_value, exc_traceback) # 保持原有的退出行为 sys.exit(1) # 替换默认的全局异常钩子 sys.excepthook = custom_excepthook print("触发异常后,常规回溯会显示代码行:") test()
运行这段代码后,输出会和traceback.print_exc()的效果一致,能清晰显示出错的代码行:
触发异常后,常规回溯会显示代码行: Traceback (most recent call last): File "test.py", line 22, in <module> test() File "<test>", line 3, in test 1/0 ZeroDivisionError: division by zero
原理说明
sys.excepthook是Python处理未捕获异常的全局入口,解释器遇到未捕获异常时会自动调用它。通过替换成我们自定义的钩子,就能让异常处理逻辑复用traceback模块的代码行读取机制,从而利用linecache中的缓存内容,展示动态生成代码的出错行。
内容的提问来源于stack exchange,提问作者asmeurer

