39

谁能解释弱引用的用法?

文档没有准确解释它,它只是说 GC 可以随时销毁通过弱引用链接到的对象。那么拥有一个可以随时消失的物体有什么意义呢?如果我需要在它消失后立即使用它怎么办?

你能用一些很好的例子来解释它们吗?

谢谢

4

3 回答 3

42

事件是弱引用的常见场景。


问题

考虑一对对象:发射器和接收器。接收器的寿命比发射器短。

您可以尝试这样的实现:

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

但是,在这种情况下,Emitter 保留对绑定方法的引用,该绑定方法callback保留对 Receiver 的引用。所以发射器保持接收器活着:

# ...continued...

del l
e.emit() # Message received: hello

这有时很麻烦。想象一下,它Emitter是某个数据模型的一部分,它指示数据何时发生变化,Receiver并由一个对话框窗口创建,该对话框窗口侦听该变化以更新某些 UI 控件。

在应用程序的生命周期中,可以生成多个对话框,我们不希望它们的接收器在窗口关闭后很长时间仍注册在 Emitter 中。那将是内存泄漏。

手动删除回调是一种选择(同样麻烦),使用弱引用是另一种选择。


解决方案

有一个很好的类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])

现在我们可以观察到预期的行为:只要 Receiver 存在,Emitter 只会保留回调。

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被释放时,回调函数也将立即被释放(并从 Emitter 中删除WeakSet),而不需要完整的 GC 循环。

于 2013-02-17T14:30:33.267 回答
28

弱引用的典型用途是如果 A 引用 B 而 B 引用 A。如果没有适当的循环检测垃圾收集器,即使没有来自“外部”。但是,如果其中一个引用是“弱”的,则对象将得到正确的 GC。

但是,Python确实有一个循环检测垃圾收集器(从 2.0 开始!),所以这不算数:)

弱引用的另一个用途是缓存。它在weakref文档中提到:

弱引用的主要用途是实现包含大对象的缓存或映射,其中不希望大对象仅仅因为它出现在缓存或映射中而保持活动状态。

如果 GC 决定销毁其中一个对象,而您需要它,则可以重新计算/重新获取数据。

于 2010-03-12T22:36:56.287 回答
1
  • 弱引用是python中的一个重要概念,在Java(java 1.5)等语言中是缺失的。
  • 在 Observer 设计模式中,通常 Observable 对象必须维护对 Observer 对象的弱引用。

    例如。A 发出一个事件 done(),B 向 A 注册,它想监听事件 done()。因此,每当发出 done() 时,都会通知 B。但是如果应用程序不需要B,那么A不能成为A中垃圾收集的障碍(因为A持有对B的引用)。因此,如果 A 持有对 B 的弱引用,并且当对 A 的所有引用都消失时,则 B 将被垃圾回收。

  • 它在实现缓存方面也非常有用。
于 2016-10-30T16:58:34.837 回答