2010-03-12 3 views
31

Qualcuno può spiegare l'uso di riferimenti deboli?Quando utilizzare i riferimenti deboli in Python?

Il documentation non lo spiega con precisione, dice solo che il GC può distruggere l'oggetto collegato tramite un riferimento debole in qualsiasi momento. Allora qual è il punto di avere un oggetto che può scomparire in qualsiasi momento? Cosa succede se ho bisogno di usarlo subito dopo che è scomparso?

Potete spiegarlo per favore con alcuni buoni esempi?

Grazie

risposta

24

L'uso tipico per i riferimenti deboli è se A ha un riferimento a B e B ha un riferimento ad A. Senza un rilevatore ciclico garbage collector, questi due oggetti sarebbero mai GC'd nemmeno se non ci sono riferimenti né dal "fuori". Tuttavia, se uno dei riferimenti è "debole", gli oggetti verranno correttamente convertiti in GC.

Tuttavia, pitone fa hanno un garbage collector rilevatore ciclico (dal 2,0!), In modo che non conta :)

Un altro uso per riferimenti deboli è di cache. E 'menzionato nella documentazione weakref:

Un uso primario per riferimenti deboli è quello di implementare le cache o mappature in possesso di oggetti di grandi dimensioni, dove è desiderabile che un grande oggetto, non essere mantenuto in vita solo perché appare in una cache o la mappatura.

Se il GC decide di distruggere uno di questi oggetti e ne avete bisogno, è possibile ricalcolare/recuperare nuovamente i dati.

+1

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

+1

Beh, era solo una menzione di cache dei cache. Ho quindi ampliato la risposta, * dopo * hai postato il tuo :) –

+0

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

25

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.

+0

Non capisco la logica dietro il set Listener._callbacks. La callback non sarà comunque mantenuta in vita per tutta la vita dell'oggetto Listener? È un metodo per quell'oggetto, dopotutto. – Xion

+0

Risulta che non lo è. 'Listener.callback' è una funzione, ma' l.callback' chiama 'yourmethod.__get __ (l, Listener) 'che restituisce un' instancemethod' callable che ricorda il suo 'sé'. Viene sempre ricreato quando viene rilevato un metodo. [Buona lettura qui] (http://users.rcn.com/python/download/Descriptor.htm#functions-and-methods) – Kos

+0

Probabilmente non si vogliono mantenere riferimenti a questo oggetto 'instancemetiond' temporaneo, ma all'oggetto 'Listener' stesso (stile 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.