我看了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
模块)甚至不会清理它,导致永久性内存泄漏。
有人可以解释这是怎么发生的吗?弱推论如何在这里有所帮助?
答案 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()
对象。