【发布时间】:2009-11-01 20:57:14
【问题描述】:
我在不同的代码库中看到过,只是在 PyMOTW 上阅读过(参见第一个注释 here)。
解释说如果将回溯分配给来自sys.exc_info()[2]的变量,则会创建一个循环,但这是为什么呢?
这是一个多大的问题?我应该在我的代码库中搜索exc_info 的所有用法并确保删除了回溯吗?
【问题讨论】:
标签: python
我在不同的代码库中看到过,只是在 PyMOTW 上阅读过(参见第一个注释 here)。
解释说如果将回溯分配给来自sys.exc_info()[2]的变量,则会创建一个循环,但这是为什么呢?
这是一个多大的问题?我应该在我的代码库中搜索exc_info 的所有用法并确保删除了回溯吗?
【问题讨论】:
标签: python
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 的变量,它指向堆栈框架本身,就像我上面展示的那样创建一个循环。
【讨论】:
[:2] 的建议自 Python 3.3 起已从文档中删除。原因在#7340 中解释。 [:2] 技巧不再起作用,应将已处理异常的 __traceback__ 属性设置为 None。相关 PEP:344、3134、3110。
回溯包含对所有活动框架的引用,而这些框架又包含对这些不同框架中所有局部变量的引用——这些引用是回溯和框架对象工作的重要组成部分,因此这不足为奇。因此,如果您将引用添加回回溯(或者在临时添加它后未能立即删除它),您不可避免地会形成一个大的引用循环——这会干扰垃圾收集(如果有任何对象可能会完全停止它)在循环中属于覆盖 __del__(终结器方法)的类。
尤其是在一个长时间运行的程序中,干扰垃圾回收并不是最好的主意,因为你会保留你并不真正需要的内存(超过必要的时间,或者如果你基本上已经通过让它们包含带有终结器的对象来阻止此类循环上的垃圾收集)。
所以,无论是否来自exc_info,最好尽快摆脱回溯!
【讨论】: