__del__() 如何干扰垃圾回收?

How does __del__() interfere with garbage collection?

我读了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)

提到

... the classes have created a reference cycle in which the reference count never drops to 0 and there is no cleanup. Not only that, the garbage collector (the gc module) won’t even clean it up, resulting in a permanent memory leak.

谁能解释一下这是怎么发生的?弱引用在这里有何帮助?

Account().observers 是一个引用 AccountObserver() 个实例的集合,但是 AccountObserver().theaccount 是一个指向 backAccount() 个实例的引用观察者存储在集合中的位置。那是一个循环引用。

正常情况下,垃圾收集器会检测到这样的循环并打破循环,允许引用计数下降到 0 并正常 clean-up 发生。但是,对于定义了 __del__ 方法的 类 有一个例外,就像 David 示例中的 类 所做的那样。来自 Python 2 gc module documenation:

gc.garbage
list of objects which the collector found to be unreachable but could not be freed (uncollectable objects). By default, this list contains only objects with __del__() methods. Objects that have __del__() methods and are part of a reference cycle cause the entire reference cycle to be uncollectable, including objects not necessarily in the cycle but reachable only from it. Python doesn’t collect such cycles automatically because, in general, it isn’t possible for Python to guess a safe order in which to run the __del__() methods.

所以圆不能被打破,因为垃圾收集器拒绝猜测首先调用哪个终结器(__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")

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

The primary benefits of this PEP regard objects with finalizers, such as objects with a __del__ method and generators with a finally block. Those objects can now be reclaimed when they are part of a reference cycle.

并不是说这不会导致回溯;如果您执行 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() 对象无论如何都会被回收。