在Python中什么时候使用弱引用?
有人能解释一下弱引用的用法吗?
文档里没有详细说明,只是说垃圾回收(GC)可以随时销毁通过弱引用链接的对象。那么,为什么还要有一个随时可能消失的对象呢?如果我在它消失后想用它怎么办?
能不能给我一些好的例子来解释一下?
谢谢
3 个回答
- 弱引用是Python中的一个重要概念,而在像Java(Java 1.5)这样的语言中是缺失的。
在观察者设计模式中,通常可观察对象(Observable Object)必须对观察者对象(Observer)保持弱引用。
举个例子,假设A发出一个事件done(),而B注册到A上,表示它想要监听这个done()事件。这样,每当done()被触发时,B就会收到通知。但是,如果B在应用中并不是必须的,那么A就不应该阻碍垃圾回收(因为A持有对B的引用)。因此,如果A对B保持的是弱引用,当所有指向A的引用都消失时,B就会被垃圾回收。
- 弱引用在实现缓存时也非常有用。
事件是使用弱引用的一个常见场景。
问题
想象一下有两个对象:发射器(Emitter)和接收器(Receiver)。接收器的生命周期比发射器短。
你可以尝试这样实现:
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
是通过一个对话框创建的,这个对话框监听这些变化以更新一些用户界面控件。
在应用程序的运行过程中,可能会打开多个对话框,我们不希望它们的接收器在窗口关闭后仍然被发射器注册着。那样会造成内存泄漏。
手动移除回调是一个选项(这也很麻烦),使用弱引用是另一个选择。
解决方案
有一个很不错的类 WeakSet
,看起来像普通的集合,但它使用弱引用来存储成员,当成员被释放时,它就不再存储它们了。
太好了!我们来用它:
def __init__(self):
self.listeners = weakref.WeakSet()
然后再运行一次:
e = Emitter()
l = Receiver(e)
e.emit()
del l
e.emit()
哦,什么都没发生!这是因为绑定的方法(特定接收器的 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])
现在我们可以观察到预期的行为:发射器只在接收器存在时才保持对回调的引用。
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
中移除),不需要完整的垃圾回收周期。
弱引用的常见用途是当对象A引用对象B,而对象B又引用对象A时。如果没有一个能检测循环引用的垃圾回收器,这两个对象就算外面没有其他地方引用它们,也永远不会被清理掉。但是如果其中一个引用是“弱引用”,那么这些对象就能被正常清理了。
不过,Python其实是有循环检测的垃圾回收器的(从2.0版本开始就有了!),所以这个情况在Python里不算问题哦 :)
弱引用的另一个用途是用来做缓存。在weakref
的文档中提到:
弱引用的一个主要用途是实现缓存或映射,存放一些大对象,这样就可以确保大对象不会因为在缓存或映射中而被一直保留。
如果垃圾回收器决定要销毁其中一个对象,而你又需要它,你可以重新计算或重新获取这些数据。