【问题标题】:When to use weak references in Python?何时在 Python 中使用弱引用?
【发布时间】:2011-01-27 00:52:28
【问题描述】:

谁能解释弱引用的用法?

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

你能用一些很好的例子解释一下吗?

谢谢

【问题讨论】:

    标签: python weak-references


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 嗯...我没有意识到您提到了缓存...我想我应该删除我的答案并下次仔细阅读:-)。
    • 好吧,这只是对缓存的单行提及。然后我扩展了答案,你发布了你的答案之后:)
    • 好吧,至少我没疯:-)。它并没有说您的帖子已被编辑。无论哪种方式..在我再次阅读后我喜欢你的回答......而且我不想表现得像渴望声誉:-)。
    【解决方案2】:

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


    问题

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

    你可以尝试这样的实现:

    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 的引用,该方法保留对接收器的引用。所以 Emitter 让 Receiver 保持活跃:

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

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

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

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


    解决方案

    有一个不错的类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循环。

    【讨论】:

    • 我不明白 Listener._callbacks 设置背后的原理。无论如何,回调不会在 Listener 对象的整个生命周期中保持活动状态吗?毕竟,它是该对象的一种方法。
    • 事实证明不是。 Listener.callback 是一个函数,但 l.callback 调用 yourmethod.__get__(l, Listener) 会返回一个 instancemethod 可调用函数,它会记住它的 self。它总是在查找方法时重新创建。 Good reading here
    • 您可能不想保留对这个瞬态instancemetiond 对象的引用,而是对Listener 对象本身(Java 风格)的引用。
    • 经过我们的讨论,我已将Listener 重命名为Receiver,因为instancemethod 对象(或来自weak_bind 的回调)实际上是在这里执行Listener 角色的原因。
    • 在“接收器保持引用......”中,在第一个例子之后,不应该是“发射器保持引用......”吗? +1,顺便说一句。
    【解决方案3】:
    • 弱引用是python中的一个重要概念,缺失的 使用 Java(java 1.5)等语言。
    • 在Observer设计模式中,通常Observable Object必须维护 对 Observer 对象的弱引用。

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

    • 它在实现缓存方面也非常有用。

    【讨论】:

    • java.lang.ref.WeakReference 是在 Java 1.2 中实现的。事实上,我是一名 Java 开发人员,我来这里是因为在 Python 中寻找弱引用缓存实现,这是弱引用 BTW 的一个非常常见的情况,由于 Phyton 的垃圾收集模型和默认配置。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多