__del __()如何干扰垃圾收集?

时间:2017-01-25 11:59:35

标签: python garbage-collection weak-references

我看了an example in David Beazley's Python Essential Reference

class Account(object):
    def __init__(self,name,balance):
         self.name = name
         self.balance = balance
         self.observers = set()
    def __del__(self):
         for ob in self.observers:
             ob.close()
         del self.observers
    def register(self,observer):
        self.observers.add(observer)
    def unregister(self,observer):
        self.observers.remove(observer)
    def notify(self):
        for ob in self.observers:
            ob.update()
    def withdraw(self,amt):
        self.balance -= amt
        self.notify()

class AccountObserver(object):
     def __init__(self, theaccount):
         self.theaccount = theaccount
         theaccount.register(self)
     def __del__(self):
         self.theaccount.unregister(self)
         del self.theaccount
     def update(self):
         print("Balance is %0.2f" % self.theaccount.balance)
     def close(self):
         print("Account no longer in use")

# Example setup
a = Account('Dave',1000.00)
a_ob = AccountObserver(a)

提到

  

...类创建了一个引用循环,其中引用计数永远不会降为0并且没有清理。不仅如此,垃圾收集器(gc模块)甚至不会清理它,导致永久性内存泄漏。

有人可以解释这是怎么发生的吗?弱推论如何在这里有所帮助?

1 个答案:

答案 0 :(得分:9)

Account().observers是引用AccountObserver()个实例的集合,但AccountObserver().theaccount是将返回指向存储观察者的Account()实例的引用在集合中。这是一个循环参考。

通常,垃圾收集器会检测到这样的圆圈并打破循环,允许引用计数降至0并进行正常清理。但是,对于定义__del__方法的类,有一个例外,正如David的示例中的类所做的那样。来自Python 2 gc module documenation

  

gc.garbage
   收集器发现无法访问但无法释放的对象列表(无法收集的对象)。默认情况下,此列表仅包含具有__del__()方法的对象。 具有__del__()方法且属于参考周期的对象会导致整个参考周期无法收集,包括不一定在周期中但只能从其中访问的对象。 Python不会自动收集这样的循环,因为通常情况下,Python无法猜测运行__del__()方法的安全顺序。

因此垃圾收集器拒绝猜测首先调用的终结器(__del__方法),所以圆圈无法被破坏。请注意,对于特定示例,随机选择一个不安全;如果您先调用Account().__del__,则会删除observers集,并且AccountObserver().__del__的后续调用将因AttributeError而失败。

弱引用不参与引用计数;因此,如果AccountObserver().theaccount使用弱引用来指向相应的Account()实例,那么如果只剩下弱引用,则Account()实例将不会保持活动状态:

class AccountObserver(object):
    def __init__(self, theaccount):
        self.theaccountref = weakref.ref(theaccount)
        theaccount.register(self)

    def __del__(self):
        theaccount = self.theaccountref()
        if theaccount is not None:    
            theaccount.unregister(self)

    def update(self):
        theaccount = self.theaccountref()
        print("Balance is %0.2f" % theaccount.balance)

    def close(self):
        print("Account no longer in use")

请注意,我链接到Python 2文档。从Python 3.4开始,这已经不再正确,甚至可以清除示例中显示的循环依赖关系,因为已经实现了PEP 442 – Safe object finalization

  

此PEP的主要优点是将对象与终结器相关联,例如具有__del__方法的对象和具有finally块的生成器。现在可以在参考周期中回收这些对象。

并不是说这不会导致追溯;如果您在Python 3.6中执行该示例,删除引用,并启动垃圾收集运行,则会因为Account().observers集可能已被删除而得到回溯:

>>> import gc
>>> del a, a_ob
>>> gc.collect()
Account no longer in use
Exception ignored in: <bound method AccountObserver.__del__ of <__main__.AccountObserver object at 0x10e36a898>>
Traceback (most recent call last):
  File "<stdin>", line 6, in __del__
  File "<stdin>", line 13, in unregister
AttributeError: 'Account' object has no attribute 'observers'
65

回溯只是一个警告,否则gc.collect()调用成功,无论如何都会获得僵尸AccountObserver()Account()对象。