Los eventos son un escenario común para las referencias débiles.
Problema
considerar un par de objetos: emisor y el receptor. El receptor tiene una vida útil más corta que el emisor.
Usted podría tratar de una implementación de esta manera:
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
Sin embargo, en este caso, el emisor mantiene una referencia a un método vinculado callback
que mantiene una referencia al receptor. Entonces el Emisor mantiene vivo el Receptor:
# ...continued...
del l
e.emit() # Message received: hello
Esto a veces es problemático. Imagine que Emitter
es parte de algún modelo de datos que indica cuándo los datos cambian y Receiver
se creó mediante una ventana de diálogo que escucha esos cambios para actualizar algunos controles de la interfaz de usuario.
A lo largo de la vida útil de la aplicación, se pueden generar múltiples diálogos y no queremos que sus receptores sigan registrados dentro del Emisor mucho después de que se haya cerrado la ventana. Eso sería una pérdida de memoria.
Eliminar las devoluciones de llamada manualmente es una opción (igual de problemática), usar referencias débiles es otra.
Solución
Hay una clase agradable WeakSet
que se parece a un juego normal, pero almacena sus miembros utilizando referencias débiles y ya no los almacena cuando son liberados.
¡Excelente! Usémoslo:
def __init__(self):
self.listeners = weakref.WeakSet()
y correr de nuevo:
e = Emitter()
l = Receiver(e)
e.emit()
del l
e.emit()
Oh, no pasa nada en absoluto! Esto se debe a que ahora el método enlazado (el receptor específico callback
) está huérfano, ni el emisor ni el receptor guardan una referencia fuerte. Por lo tanto, es basura recogida de inmediato.
Hagamos el receptor (no el emisor esta vez) mantener una fuerte referencia a esta devolución de llamada:
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])
Ahora podemos observar el comportamiento esperado: el emisor sólo mantiene la devolución de llamada, siempre y cuando la vida del receptor .
e = Emitter()
l = Receiver(e)
assert len(e.listeners) == 1
del l
import gc; gc.collect()
assert len(e.listeners) == 0
Bajo el capó
Tenga en cuenta que he tenido que poner un gc.collect()
aquí para asegurarse de que el receptor está realmente limpiarse inmediatamente. Es necesario aquí porque ahora hay un ciclo de referencias fuertes: el método vinculado se refiere al receptor y viceversa.
Esto no es muy malo; esto solo significa que la limpieza del receptor se aplazará hasta que se ejecute el siguiente recolector de basura. Las referencias cíclicas no se pueden limpiar con el simple mecanismo de conteo de referencias.
Si realmente desea, puede eliminar el ciclo de referencia fuerte reemplazando el método enlazado con un objeto de función personalizado que mantendría su self
como una referencia débil también.
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])
Pongamos que la lógica en una función auxiliar:
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])
Ahora no hay ciclo de referencias fuertes, por lo que cuando Receiver
se libera, también serán liberados de la función de devolución de llamada (y se retira del emisor WeakSet
) inmediatamente, sin la necesidad de un ciclo GC completo.
Hmm ... No me di cuenta de que mencionas cachés ... Supongo que debería eliminar mi respuesta y leer más detenidamente la próxima vez :-). – Tom
Bueno, fue solo una mención de una línea de cachés. Luego expandí la respuesta, * después de * que habías publicado la tuya :) –
Ok, bueno que al menos no estoy loco :-). Sin embargo, no dice que tu publicación haya sido editada. De cualquier manera ... Me gustó tu respuesta después de que la leí de nuevo ... y no quiero parecer hambrienta de reputación :-). – Tom