2010-03-12 10 views
31

¿Alguien puede explicar el uso de referencias débiles?Cuándo usar referencias débiles en Python?

El documentation no lo explica con precisión, simplemente dice que el GC puede destruir el objeto vinculado a través de una referencia débil en cualquier momento. Entonces, ¿de qué sirve tener un objeto que pueda desaparecer en cualquier momento? ¿Qué pasa si necesito usarlo justo después de que desapareció?

¿Puede explicarlos con algunos buenos ejemplos?

Gracias

Respuesta

24

El uso típico de referencias débiles es si A tiene una referencia a B y B tiene una referencia a A. Sin un recolector de basura ciclo de detección adecuado, esos dos objetos nunca llegaría incluso GC'd si no hay referencias al "afuera". Sin embargo, si una de las referencias es "débil", los objetos se GC'd correctamente.

Sin embargo, Python hace tienen un recolector de basura ciclo de detección (desde 2,0!), Por lo que no cuenta :)

Otro uso de referencias débiles es cachés. Es mencionado en la documentación weakref:

Un uso principal de referencias débiles es implementar cachés o asignaciones que sostienen objetos de gran tamaño, donde se desea que un objeto grande no puede ser mantenido con vida únicamente porque aparece en una memoria caché o mapeo.

Si el GC decide destruir uno de esos objetos, y lo necesita, puede simplemente recalcular/volver a buscar los datos.

+1

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

+1

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 :) –

+0

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

25

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.

+0

No entiendo el razonamiento detrás de Listener._callbacks establecido. ¿No se mantendrá activa la devolución de llamada durante toda la vida del objeto Listener de todos modos? Es un método para ese objeto, después de todo. – Xion

+0

Resulta que no lo es. 'Listener.callback' es una función, pero' l.callback' llama a 'yourmethod.__get __ (l, Listener) 'que devuelve un' instancemethod' invocable que recuerda su 'self'. Siempre se recrea cuando se busca un método. [Lectura buena aquí] (http://users.rcn.com/python/download/Descriptor.htm#functions-and-methods) – Kos

+0

Probablemente no desee mantener referencias a este objeto transient 'instancemetiond', pero al objeto 'Listener' en sí (estilo Java). – Xion

1
- Weak references is an important concept in python, which is missing 
    in languages likes Java(java 1.5). 
- In Observer design pattern, generally Observable Object must maintain 
    weak references to the Observer object. 

    eg. A emits an event done() and B registers with A that, it want to 
    listen to event done(). Thus, whenever done() is emitted, B is 
    notified. But If B isn't required in application, then A must not 
    become an hinderance in the garbage collection in A(since A hold the 
    reference to B). Thus, if A has hold weak reference to B, and when 
    all the references to A are away, then B will be garbage collected. 
- It's also very useful in implementing caches. 
Cuestiones relacionadas