为什么需要显式删除sys.exc_info()的回溯?
我在不同的代码库中看到过,也在PyMOTW上读到过(请看第一个备注这里)。
解释说,如果把错误追踪信息(traceback)赋值给一个变量,来自于sys.exc_info()[2]
,就会产生一个循环,但这是为什么呢?
这个问题有多严重?我需要在我的代码中搜索所有exc_info
的使用情况,确保删除追踪信息吗?
2 个回答
错误追踪信息里包含了所有活跃的调用栈,这些调用栈又包含了各种局部变量的引用。这些引用是错误追踪和调用栈对象工作的重要部分,所以这并不奇怪。如果你在追踪信息中添加了一个引用(或者在临时添加后没有及时移除),就会形成一个大的引用循环。这会影响垃圾回收(如果循环中的任何对象属于重写了__del__
方法的类,可能会完全阻止垃圾回收)。
特别是在一个长时间运行的程序中,干扰垃圾回收可不是个好主意,因为你会占用一些其实不需要的内存(如果这些循环中有带有终结器的对象,可能会让你无限期地占用内存)。
所以,尽量在可行的情况下尽快处理掉错误追踪信息,无论它们是来自exc_info
还是其他地方!
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
的变量,它指向栈帧本身,这就创建了一个循环,就像我上面展示的那样。