为什么需要显式删除sys.exc_info()的回溯?

14 投票
2 回答
3949 浏览
提问于 2025-04-15 15:34

我在不同的代码库中看到过,也在PyMOTW上读到过(请看第一个备注这里)。

解释说,如果把错误追踪信息(traceback)赋值给一个变量,来自于sys.exc_info()[2],就会产生一个循环,但这是为什么呢?

这个问题有多严重?我需要在我的代码中搜索所有exc_info的使用情况,确保删除追踪信息吗?

2 个回答

12

错误追踪信息里包含了所有活跃的调用栈,这些调用栈又包含了各种局部变量的引用。这些引用是错误追踪和调用栈对象工作的重要部分,所以这并不奇怪。如果你在追踪信息中添加了一个引用(或者在临时添加后没有及时移除),就会形成一个大的引用循环。这会影响垃圾回收(如果循环中的任何对象属于重写了__del__方法的类,可能会完全阻止垃圾回收)。

特别是在一个长时间运行的程序中,干扰垃圾回收可不是个好主意,因为你会占用一些其实不需要的内存(如果这些循环中有带有终结器的对象,可能会让你无限期地占用内存)。

所以,尽量在可行的情况下尽快处理掉错误追踪信息,无论它们是来自exc_info还是其他地方!

23

Python 3(更新原始回答):

在Python 3中,问题中引用的建议已经从Python文档中删除。我的原始回答(如下)仅适用于包含该引用的Python版本。

Python 2:

Python的垃圾回收机制最终会找到并删除循环引用,比如从某个栈帧内部引用回溯栈时产生的那种情况,所以你不用去修改你的代码。不过,往后你可以参考一下

http://docs.python.org/library/sys.html

(这里记录了exc_info())并说:

exctype, value = sys.exc_info()[:2]

当你需要获取异常信息时。

再说两点:

首先,你为什么要运行exc_info()呢?

如果你想捕获异常,难道不应该直接这样说:

try:
    ...
except Exception as e:  # or "Exception, e" in old Pythons
    ... do with with e ...

而不是在sys模块里纠结对象?

第二:好吧,我给了很多建议,但其实没有真正回答你的问题。:-)

那么,为什么会产生循环呢?简单来说,当一个对象引用自己时,就会产生循环:

a = [1,2,3]
a.append(a)

或者当两个对象互相引用时:

a = [1,2,3]
b = [4,5,a]
a.append(b)

在这两种情况下,当函数结束时,变量的值仍然存在,因为它们被锁定在一个引用计数的循环中:一个对象不能消失,直到另一个先消失!只有现代的Python垃圾回收机制才能解决这个问题,它会最终发现这个循环并打破它。

所以,理解这个情况的关键在于,“回溯”对象——也就是exc_info()返回的第三个东西(索引#2)——包含了每个在异常被调用时处于活动状态的函数的“栈帧”。而这些栈帧并不是“死”的对象,它们显示的是在异常被调用时的状态;这些帧仍然是活的!捕获异常的函数仍然在运行,所以它的栈帧是一个活的东西,随着代码执行来处理异常(以及完成“except”部分后继续做其他事情),它仍在增长和失去变量引用。

所以,当你说t = sys.exc_info()[2]时,回溯中的一个栈帧——实际上是当前正在运行的函数的栈帧——现在有一个名为t的变量,它指向栈帧本身,这就创建了一个循环,就像我上面展示的那样。

撰写回答