2009-10-02 9 views
42

Ho qualche codice in cui le istanze di classi hanno genitore < - riferimenti> bambino gli uni agli altri, per esempio:Come e quando usare in modo appropriato weakref in Python

class Node(object): 
    def __init__(self): 
    self.parent = None 
    self.children = {} 
    def AddChild(self, name, child): 
    child.parent = self 
    self.children[name] = child 

def Run(): 
    root, c1, c2 = Node(), Node(), Node() 
    root.AddChild("first", c1) 
    root.AddChild("second", c2) 
Run() 

ho che questo crea i riferimenti circolari tali che root, c1 e c2 non saranno liberati dopo che Run() è completato, giusto ?. Quindi, come farli liberare? Penso di poter fare qualcosa come root.children.clear() o self.parent = None - ma cosa succede se non so quando farlo?

È un momento appropriato per utilizzare il modulo weakref? Cosa, esattamente, mi indebolisco? l'attributo parent? L'attributo children? L'intero oggetto? Tutti i precedenti? Vedo parlare di WeakKeyDictionary e weakref.proxy, ma non mi è chiaro come debbano essere usati, se non del tutto, in questo caso.

Questo è anche su python2.4 (impossibile aggiornare).

Aggiornamento: Esempio di Sintesi

Nei oppone weakref-ify dipende da quale oggetto può vivere senza l'altro, e quali oggetti dipendono l'uno dall'altro. L'oggetto che vive più a lungo dovrebbe contenere oggetti deboli per gli oggetti a vita più breve. Allo stesso modo, le deboli riforme non dovrebbero essere indirizzate alle dipendenze: se lo sono, la dipendenza potrebbe scomparire silenziosamente anche se è ancora necessaria.

Se, per esempio, si ha una struttura ad albero, root, che ha figli, kids, ma può esistere senza bambini, allora l'oggetto root dovrebbe usare weakrefs per la sua kids. Questo è anche il caso se l'oggetto figlio dipende dall'esistenza dell'oggetto genitore. Di seguito, l'oggetto secondario richiede un genitore per calcolarne la profondità, quindi il riferimento forte per parent. I membri dell'attributo kids sono facoltativi, tuttavia, in questo modo vengono utilizzati i criteri deboli per impedire un riferimento circolare.

class Node: 
    def __init__(self) 
    self.parent = None 
    self.kids = weakref.WeakValueDictionary() 
    def GetDepth(self): 
    root, depth = self, 0 
    while root: 
     depth += 1 
     root = root.parent 
    return depth 
root = Node() 
root.kids["one"] = Node() 
root.kids["two"] = Node() 
# do what you will with root or sub-trees of it. 

Per capovolgere il rapporto in giro, abbiamo qualcosa di simile alla seguente. Qui, le classi Facade richiedono un'istanza Subsystem per funzionare, quindi utilizzano un riferimento forte al sottosistema di cui hanno bisogno. Subsystem s, tuttavia, non è necessario un Facade per funzionare. Subsystem s fornisce semplicemente un modo per notificare le azioni di ciascuno Facade s.

class Facade: 
    def __init__(self, subsystem) 
    self.subsystem = subsystem 
    subsystem.Register(self) 

class Subsystem: 
    def __init__(self): 
    self.notify = [] 
    def Register(self, who): 
    self.notify.append(weakref.proxy(who)) 

sub = Subsystem() 
f1 = CliFacade(sub) 
f2 = WebFacade(sub) 
# Go on to reading from POST, stdin, etc 

risposta

25

Sì, weakref è eccellente qui. In particolare, invece di:

self.children = {} 

uso:

self.children = weakref.WeakValueDictionary() 

, non è necessario altro cambiamento nel codice. In questo modo, quando un bambino non ha altre differenze, va semplicemente via - e così fa la voce nella mappa children del genitore che ha quel bambino come valore.

Evitare loop di riferimento è in alto alla pari con l'implementazione di cache come motivazione per l'utilizzo del modulo weakref. I loop di Ref non ti uccideranno, ma potrebbero finire per intasare la tua memoria, esp.se alcune delle classi in cui sono coinvolte le istanze definiscono __del__, poiché ciò interferisce con la capacità del modulo gc di dissolvere tali loop.

+0

Inoltre, se sei sicuro di non aver bisogno del gc ciclico, puoi disabilitarlo per un piccolo aumento di prestazioni . –

+1

Grazie, Alex. C'è una ragione specifica per weakref 'children' piuttosto che' parent'? L'effetto sarebbe lo stesso? Cosa accadrebbe se anche il 'genitore 'fosse debole? Nel caso di una lista a doppio collegamento, dovremmo 'prev',' next', o entrambi essere weakrefs? –

+5

Questo è un cattivo suggerimento. Tutti i bambini nell'esempio verranno distrutti subito dopo il ritorno da "Run()". In generale si lega quasi sempre una radice della struttura alla variabile, quindi il modo corretto è usare 'weakref' per' parent', ma non 'children'. –

13

Suggerisco di utilizzare child.parent = weakref.proxy(self). Questa è una buona soluzione per evitare riferimenti circolari nel caso in cui la durata di (riferimenti esterni a) parent copre la durata di child. Al contrario, utilizzare weakref per child (come suggerito da Alex) quando la durata di vita di child copre la durata di parent. Ma non utilizzare mai weakref quando sia parent sia child possono essere vivi senza altro.

Qui queste regole sono illustrate con esempi. Utilizzare genitore weakref-ed se si memorizza radice in qualche variabile e passare in giro, mentre i bambini si accede da esso:

def Run(): 
    root, c1, c2 = Node(), Node(), Node() 
    root.AddChild("first", c1) 
    root.AddChild("second", c2) 
    return root # Note that only root refers to c1 and c2 after return, 
       # so this references should be strong 

usare i bambini weakref-ed se si associa tutti loro di variabili, mentre la radice si accede attraverso di loro :

def Run(): 
    root, c1, c2 = Node(), Node(), Node() 
    root.AddChild("first", c1) 
    root.AddChild("second", c2) 
    return c1, c2 

Ma nessuno lavorerà per il seguente:

def Run(): 
    root, c1, c2 = Node(), Node(), Node() 
    root.AddChild("first", c1) 
    root.AddChild("second", c2) 
    return c1 
+2

Ci sono un sacco di casi in cui uno o entrambi i figli e il genitore possono essere vivi senza l'altro, tanto più che altre entità mantengono ancora riferimenti a loro_; questo è il momento in cui potresti voler usare riferimenti reciprocamente deboli (questi altri riferimenti esterni faranno il lavoro di mantenere in vita le entità esattamente il tempo necessario). –

+2

** "Suggerisco di usare' child.parent = weakref.proxy (self) '." ** _Thisss._ Questo è l'approccio canonico per il caso comune di un 'padre' longevo che contiene un bambino' di breve durata ' '-ren. La soluzione di Alexis è più applicabile al caso limite di più "figli" di lunga vita che "possiedono" un "genitore" di breve durata, che vedo solo raramente in natura. –

1

ho voluto chiarire quali riferimenti possono essere debole. Il seguente approccio è generale, ma io uso l'albero doppiamente collegato in tutti gli esempi.

passo logico 1.

È necessario assicurarsi che ci sono forti riferimenti per mantenere tutti gli oggetti in vita fino a quando ne avete bisogno. Potrebbe essere fatto in molti modi, per esempio:

  • [nomi diretti]: un nome di riferimento per ogni nodo nell'albero
  • [container]: un riferimento a un contenitore che memorizza tutti i nodi
  • [Root + bambini]: un riferimento al nodo principale, e riferimenti da ogni nodo per i suoi figli
  • [foglie + genitore]: i riferimenti a tutti i nodi foglia, e riferimenti da ogni nodo al suo genitore

Passo logico 2.

Ora si aggiungono riferimenti per rappresentare informazioni, se necessario.

Ad esempio, se si è utilizzato l'approccio [contenitore] nel passaggio 1, è comunque necessario rappresentare i bordi. Un bordo tra i nodi A e B può essere rappresentato con un riferimento singolo; può andare in entrambe le direzioni. Anche in questo caso, ci sono molte opzioni, ad esempio:

  • [i bambini]: riferimenti da ogni nodo per i suoi figli
  • [parent]: un riferimento da ogni nodo al suo genitore
  • [insieme di insiemi] : un set contenente set di 2 elementi; ogni 2-elemento contiene i riferimenti ai nodi di un bordo

Naturalmente, se si è utilizzato [root + bambini] approccio al punto 1, tutte le vostre informazioni siano già pienamente rappresentato, in modo da saltare questo passaggio.

passo logico 3.

Ora si aggiungono i riferimenti per migliorare le prestazioni, se lo si desidera.

Ad esempio, se si utilizza l'approccio [contenitore] nel passaggio 1 e [i bambini] si avvicinano al passaggio 2, è possibile migliorare la velocità di determinati algoritmi e aggiungere riferimenti tra ciascun nodo e il relativo genitore. Tali informazioni sono logicamente ridondanti, poiché è possibile (a un costo in termini di prestazioni) derivare da dati esistenti.


Tutti i riferimenti in Fase 1 devono essere forti.

Tutti i riferimenti nei passaggi 2 e 3 possono essere deboli o forti. Non vi è alcun vantaggio nell'utilizzare riferimenti forti. C'è un vantaggio nell'usare riferimenti deboli finché non si è certi che i cicli non sono più possibili. A rigor di termini, una volta che sai che i cicli sono impossibili, non fa alcuna differenza se utilizzare riferimenti deboli o forti. Ma per evitare di pensarci, potresti anche usare solo riferimenti deboli nei passaggi 2 e 3.

Problemi correlati