2009-11-01 40 views
11

我在不同的代码库中看到过,只是在PyMOTW上阅读(请参阅第一个注释here)。为什么需要显式删除sys.exc_info()回溯?

该解释说,如果回溯被分配给来自sys.exc_info()[2]的变量,将创建一个循环,但为什么?

这个问题有多大?我是否应该在我的代码库中搜索exc_info的所有用途,并确保回溯被删除?

回答

19

的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垃圾收集器才能解决这个问题,最终注意到循环并打破它。

所以理解这种情况的关键在于一个“追溯”对象 - 第三件事(在索引#2处)由exc_info()返回 - 包含每个函数的“栈帧”,当调用异常时该函数处于活动状态。并且这些堆栈帧是而不是“dead”对象,显示调用execption时;帧仍然活着!捕捉到异常的函数仍然存在,所以它的堆栈框架是一个有生命的东西,它仍然在不断增长并且在其代码执行时会丢失变量引用以处理异常(并且在完成“except”子句时执行其他操作关于它的工作)。

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

+1

[警告](https://docs.python.org/3.2/library/sys.html#sys.exc_info)您提到的使用'[:2]'的建议'已从文档中删除,因为Python 3.3。原因在[#7340](https://bugs.python.org/issue7340)中解释。 '[:2]'技巧不再有效,''处理的异常的__traceback__'属性应该被设置为'None'。相关的PEP:[344](https://www.python.org/dev/peps/pep-0344/#open-issue-garbage-collection),[3134](https://www.python.org/dev/peps/pep-3134 /#open-issue-garbage-collection),[3110](https://www.python.org/dev/peps/pep-3110/#semantic-changes)。 – Delgan 2017-12-09 10:56:11

10

追踪包含对所有活动框架的引用,这些框架又包含对这些各种框架中所有局部变量的引用 - 这些引用是追踪和框架对象的大部分工作,所以这并不令人惊讶。所以,如果你添加一个引用回溯回溯(或者暂时添加它时没有及时删除它),你不可避免地会形成一个很大的引用循环 - 这会干扰垃圾回收(如果有任何对象会干扰它)在循环中属于覆盖__del__的类,即终结器方法)。

特别是在一个长期运行的程序中,干扰垃圾收集并不是最好的想法,因为你将会记住你并不真正需要的东西(超过必要的时间,或者无限期地基本上通过让这些循环包含具有终结器的对象来阻止垃圾回收)。

所以,最好尽快摆脱追溯,无论它们是否来自exc_info