何时在Python中使用弱引用?

bod*_*ydo 35 python weak-references

任何人都可以解释弱引用的用法吗?

文件没有精确地解释它,它只是说,GC可以摧毁通过随时弱引用链接到对象.那么一个物体可以随时消失的重点是什么?如果我需要在消失后立即使用它怎么办?

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

谢谢

Kos*_*Kos 36

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


问题

考虑一对物体:发射器和接收器.接收器的寿命比发射器短.

您可以尝试这样的实现:

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
Run Code Online (Sandbox Code Playgroud)

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

# ...continued...

del l
e.emit() # Message received: hello
Run Code Online (Sandbox Code Playgroud)

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

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

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


有一个很好的类WeakSet看起来像普通集,但使用弱引用存储其成员,并且在释放它们时不再存储它们.

优秀!我们来使用它:

def __init__(self):
    self.listeners = weakref.WeakSet()
Run Code Online (Sandbox Code Playgroud)

并再次运行:

e = Emitter()
l = Receiver(e)
e.emit()
del l
e.emit()
Run Code Online (Sandbox Code Playgroud)

哦,什么都没发生!那是因为绑定方法(特定接收器callback)现在是孤立的 - 发射器和接收器都没有强烈的引用.因此,立即收集垃圾.

让我们让接收者(这次不是发射者)对这个回调保持强烈的引用:

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])
Run Code Online (Sandbox Code Playgroud)

现在我们可以观察到预期的行为:只要接收器存在,发射器就只保留回调.

e = Emitter()
l = Receiver(e)
assert len(e.listeners) == 1

del l
import gc; gc.collect()
assert len(e.listeners) == 0
Run Code Online (Sandbox Code Playgroud)

在引擎盖下

请注意,我必须放在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])
Run Code Online (Sandbox Code Playgroud)

让我们把这个逻辑放到辅助函数中:

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])
Run Code Online (Sandbox Code Playgroud)

现在没有强引用的循环,所以当Receiver释放时,回调函数也将WeakSet立即被释放(并从发射器中移除),而不需要完整的GC循环.


Nic*_*lás 25

弱引用的典型用法是如果A有对B的引用而B有对A的引用.如果没有适当的循环检测垃圾收集器,即使没有引用任何一个,这两个对象也永远不会得到GC. "外".但是,如果其中一个引用是"弱",则对象将正确地进行GC.

但是,Python 确实有一个循环检测垃圾收集器(自2.0!),因此不计算:)

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

弱引用的主要用途是实现保存大对象的高速缓存或映射,其中希望大对象不能仅仅因为它出现在高速缓存或映射中而保持活动.

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