事件是弱引用的常见场景。
Problem
考虑一对对象:发射器和接收器。接收器的寿命比发射器短。
你可以尝试这样的实现:
class Emitter(object):
def __init__(self):
self.listeners = set()
def emit(self):
for listener in self.listeners:
# Notify
listener('hello')
class Receiver(object):
def __init__(self, emitter):
emitter.listeners.add(self.callback)
def callback(self, msg):
print 'Message received:', msg
e = Emitter()
l = Receiver(e)
e.emit() # Message received: hello
然而,在这种情况下,发射器保留对绑定方法的引用callback
保留对接收者的引用。因此发射器使接收器保持活动状态:
# ...continued...
del l
e.emit() # Message received: hello
这有时很麻烦。想象一下Emitter
是某些数据模型的一部分,指示数据何时发生变化Receiver
由一个对话窗口创建,该窗口监听更改以更新某些 UI 控件。
在应用程序的生命周期中,可以生成多个对话框,并且我们不希望在窗口关闭很久之后它们的接收器仍然在发射器内注册。那将是内存泄漏。
手动删除回调是一种选择(同样麻烦),使用弱引用是另一种选择。
Solution
有一个很好的班级WeakSet
它看起来像一个普通的集合,但使用弱引用存储其成员,并且在释放它们时不再存储它们。
出色的!让我们使用它:
def __init__(self):
self.listeners = weakref.WeakSet()
并再次运行:
e = Emitter()
l = Receiver(e)
e.emit()
del l
e.emit()
噢,什么也没发生!这是因为绑定方法(特定接收者的callback
) 现在是孤立的 - 发射器和接收器都没有对它有强引用。因此它会立即被垃圾收集。
让我们让 Receiver(这次不是 Emitter)保留对此回调的强引用:
class Receiver(object):
def __init__(self, emitter):
# Create the bound method object
cb = self.callback
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
现在我们可以观察到预期的行为:发射器仅在接收器存在期间保留回调。
e = Emitter()
l = Receiver(e)
assert len(e.listeners) == 1
del l
import gc; gc.collect()
assert len(e.listeners) == 0
在引擎盖下
请注意,我必须放一个gc.collect()
这里要确保接收器确实立即清理干净。这里需要它,因为现在存在一个强引用的循环:绑定方法引用接收者,反之亦然。
这还算不错。这仅意味着接收器的清理将推迟到下一次垃圾收集器运行。简单的引用计数机制无法清除循环引用。
如果您确实想要,您可以通过将绑定方法替换为自定义函数对象来删除强引用循环,该自定义函数对象将保留其self
也作为弱引用。
def __init__(self, emitter):
# Create the bound method object
weakself = weakref.ref(self)
def cb(msg):
self = weakself()
self.callback(msg)
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
让我们将该逻辑放入辅助函数中:
def weak_bind(instancemethod):
weakref_self = weakref.ref(instancemethod.im_self)
func = instancemethod.im_func
def callback(*args, **kwargs):
self = weakref_self()
bound = func.__get__(self)
return bound(*args, **kwargs)
return callback
class Receiver(object):
def __init__(self, emitter):
cb = weak_bind(self.callback)
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
现在没有强引用的循环,所以当Receiver
被释放,回调函数也将被释放(并从发射器的WeakSet
)立即,无需完整的 GC 周期。