# -*- coding: utf-8 -*-
class tA():
def __init__(self):
print 'AAA'
def __del__(self):
print 'BBB'
class tC(tA):
def __init__(self, a, b=0):
tA.__init__(self)
self.b=b # 1. del this ok
class tD(tC):
def __init__(self):
a=1
#tC.__init__(self, a) # 2. use this ok
tC.__init__(self, a, self.func) # 3. use this not ok
#tC.__init__(self, a, 3) # 4. use this ok
def func(self, pos):
pass
if __name__ == '__main__':
tD()
为什么没有BBB'输出
如果我知道#1,则输出正常
如果我使用#2或#4,则输出正常
如果我使用#3,则输出没有' BBB'?
答案 0 :(得分:6)
因为您的'BBB'
打印了班级的终结器(__del__
功能)。当垃圾收集器收集你的对象时运行终结器。
Python使用双重策略进行垃圾收集:引用计数和循环检测。引用计数达到0的对象立即被收集,但如果它们参与一个循环,那么它们的计数将永远不会达到0.然后定期调用的GC循环检测程序将最终检测到并释放所有悬空对象。 / p>
在您的特定代码中,案例#3会创建一个参考周期:self.b
是对self.func
的引用。但GC循环检测永远不会运行,因为程序在有机会之前就结束了。
但即使GC运行,具有终结器的对象也有特殊规则。来自documentation:
具有
__del__()
方法且属于参考周期的对象会导致整个参考周期无法收集,包括不一定在周期中但只能从中获取的对象。 Python不会自动收集这样的循环,因为一般来说,Python无法猜测运行__del__()
方法的安全顺序。
此外,来自here
版本3.4中更改:在PEP 442之后,使用
__del__()
方法的对象不再在gc.garbage
中结束。
所以,看起来,在3.4之前的Python中,在带有终结器的类中,你必须手动打破周期:
如果您知道安全订单,则可以通过检查垃圾清单强制解决问题,并明确地打破由于列表中的对象而导致的周期。请注意,这些对象即使存在于垃圾列表中也会保持活动状态,因此它们也应该从垃圾中删除。例如,在打破周期后,执行
del gc.garbage[:]
清空列表。
答案 1 :(得分:2)
因为func
是一个绑定方法,因此间接引用它所绑定的对象,这意味着你正在创建一个引用循环。
您可以通过执行以下操作进行验证:
...
if __name__ == '__main__':
import sys
print(sys.getrefcount(tD()))
应该在案例#2和#4中打印1
,在案例#3中打印2
。
__del__
的文档中有关于参考周期的说明:
[...]可能阻止对象的引用计数变为零的一些常见情况包括:对象之间的循环引用(例如,双向链表或具有父指针和子指针的树数据结构);对捕获异常的函数的堆栈帧上的对象的引用(存储在sys.exc_traceback中的回溯使堆栈帧保持活动状态);或者对交互模式中引发未处理异常的堆栈帧上的对象的引用(存储在sys.last_traceback中的回溯使堆栈帧保持活动状态)
[...]
启用选项周期检测器时会检测到无效的循环引用(默认情况下它是打开的),但只有在没有涉及Python级__del__()
方法的情况下才能清除。
基本上,这意味着如果你有__del__
方法,它会阻止包含引用周期的对象被清理。