Gli eventi sono uno scenario comune per riferimenti deboli.
Problema
considerare un paio di oggetti: emettitore e ricevitore. Il ricevitore ha una durata inferiore rispetto all'emettitore.
Si potrebbe provare un'implementazione in questo modo:
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
Tuttavia, in questo caso, l'emettitore mantiene un riferimento a un metodo vincolato callback
che mantiene un riferimento al ricevitore. Quindi l'Emettitore mantiene vivo il ricevitore:
# ...continued...
del l
e.emit() # Message received: hello
Questo a volte è problematico. Immaginate che Emitter
sia una parte di un modello di dati che indica quando i dati cambiano e Receiver
è stato creato da una finestra di dialogo che ascolta le modifiche per aggiornare alcuni controlli dell'interfaccia utente.
Attraverso la vita dell'applicazione, è possibile generare più finestre di dialogo e non vogliamo che i loro ricevitori siano ancora registrati all'interno dell'Emettitore molto tempo dopo che la finestra è stata chiusa. Sarebbe una perdita di memoria.
Rimuovere le callback manualmente è un'opzione (altrettanto problematica), l'utilizzo di riferimenti deboli è un altro.
Soluzione
C'è una bella classe WeakSet
che assomiglia a un set normale, ma memorizza i suoi membri utilizzando riferimenti deboli e non li memorizza quando vengono liberati.
Eccellente! Lo si può usare:
def __init__(self):
self.listeners = weakref.WeakSet()
ed eseguire di nuovo:
e = Emitter()
l = Receiver(e)
e.emit()
del l
e.emit()
Oh, non succede niente affatto! Questo perché il metodo associato (uno specifico ricevitore callback
) è orfano ora - né l'Emettitore né il Destinatario hanno un forte riferimento ad esso. Quindi è la raccolta dei rifiuti immediatamente.
Facciamo il ricevitore (non l'emettitore questa volta) mantenere un forte riferimento alla presente 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])
Ora possiamo osservare il comportamento previsto: l'emettitore mantiene solo la richiamata finché le vite Ricevitore .
e = Emitter()
l = Receiver(e)
assert len(e.listeners) == 1
del l
import gc; gc.collect()
assert len(e.listeners) == 0
Sotto il cofano
Nota che ho dovuto mettere un gc.collect()
qui per fare in modo che il ricevitore sia davvero ripulito immediatamente. È necessario qui perché ora c'è un ciclo di riferimenti forti: il metodo vincolato si riferisce al ricevente e viceversa.
Questo non è male; questo significa solo che la pulizia del ricevitore sarà rimandata finché non verrà eseguito il prossimo garbage collector. I riferimenti ciclici non possono essere eliminati dal semplice meccanismo di conteggio dei riferimenti.
Se si desidera realmente, è possibile rimuovere il ciclo di riferimento forte sostituendo il metodo associato con un oggetto funzione personalizzato che manterrebbe il suo self
come riferimento debole.
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])
Mettiamola che la logica in una funzione di supporto:
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])
Ora non c'è nessun ciclo di riferimenti forti, in modo che quando Receiver
si libera, la funzione di callback saranno anche liberati (e rimosso dal emettitore di WeakSet
) immediatamente, senza la necessità di un ciclo GC completo.
Hmm ... Non mi sono accorto che hai menzionato le cache ... Suppongo che dovrei cancellare la mia risposta e leggere più attentamente la prossima volta :-). – Tom
Beh, era solo una menzione di cache dei cache. Ho quindi ampliato la risposta, * dopo * hai postato il tuo :) –
Ok, beh, almeno non sono pazzo :-). Non dice che il tuo post è stato modificato però. Ad ogni modo .. Mi è piaciuta la tua risposta dopo averla letta di nuovo ... e non voglio venire fuori come affamata di reputazione :-). – Tom