我在David Beazley的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
模块)甚至不会清理它,导致永久性内存泄漏.
有谁可以解释这是怎么发生的?弱推理如何在这里有所帮助?
Account().observers
是一组参考AccountObserver()
实例,但AccountObserver().theaccount
是指向一个参考回至Account()
其中观察者被存储在该组实例.这是一个循环参考.
通常,垃圾收集器将检测此类圆圈并打破循环,允许引用计数降至0并进行正常清理.但是,对于定义__del__
方法的类,有一个例外,正如David的示例中的类所做的那样.从Python 2 gc
模块文档:
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 -已经实现了安全对象终结,也会清除示例中显示的循环依赖关系:
此PEP的主要优点是将对象与终结器相关联,例如带有
__del__
方法的对象和带有finally块的生成器.现在可以在参考周期中回收这些对象.
并不是说这不会导致追溯; 如果您在Python 3.6中执行该示例,删除引用,并启动垃圾回收运行,您将获得回溯,因为该Account().observers
集可能已被删除:
>>> import gc >>> del a, a_ob >>> gc.collect() Account no longer in use Exception ignored in:> Traceback (most recent call last): File " ", line 6, in __del__ File " ", line 13, in unregister AttributeError: 'Account' object has no attribute 'observers' 65
回溯只是一个警告,否则,gc.collect()
调用成功,无论如何都会获得僵尸AccountObserver()
和Account()
对象.