为什么我无法获得期望输出&BBB'?

时间:2015-07-06 07:50:53

标签: python

# -*- 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'?

2 个答案:

答案 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__方法,它会阻止包含引用周期的对象被清理。