2009-03-01 10 views
9

Ho un progetto in cui sto cercando di utilizzare weakrefs con i callback e non capisco cosa sto facendo male. Ho creato un test semplificato che mostra il comportamento esatto con cui sono confuso.Perché la funzione weakref non funziona su questo metodo associato?

Perché in questo test test_a funziona come previsto, ma il weakref per self.MyCallbackB scompare tra l'inizializzazione della classe e la chiamata test_b? Ho pensato che finché esiste l'istanza (a), il riferimento a self.MyCallbackB dovrebbe esistere, ma non lo è.

import weakref 

class A(object): 
    def __init__(self): 

     def MyCallbackA(): 
      print 'MyCallbackA' 
     self.MyCallbackA = MyCallbackA 

     self._testA = weakref.proxy(self.MyCallbackA) 
     self._testB = weakref.proxy(self.MyCallbackB) 

    def MyCallbackB(self): 
     print 'MyCallbackB' 

    def test_a(self): 
     self._testA() 

    def test_b(self): 
     self._testB() 

if __name__ == '__main__': 
    a = A()  
    a.test_a() 
    a.test_b() 

risposta

10

Si desidera un WeakMethod.

Una spiegazione per cui la soluzione non funziona può essere trovato nella discussione della ricetta:

weakref.refs normali ai metodi bound abbastanza non funzionano il modo in cui ci si aspetta, perché i metodi bound sono oggetti di prima classe; weakrefs ai metodi associati sono dead-on-arrival a meno che non esista qualche altro riferimento forte allo stesso metodo associato.

4

Secondo la documentazione del modulo weakref:

Nel seguito, il termine referente significa che l'oggetto che viene indicato da un riferimento debole.

Un riferimento debole per un oggetto non è sufficiente per tenere in vita l'oggetto: quando gli unici rimasti riferimenti a un referente sono riferimenti deboli, spazzatura raccolta è libera di distruggere il referente e riutilizzare la sua memoria per qualcos'altro.

Che cosa sta accadendo con MyCallbackA è che si sta tenendo un riferimento ad essa nei casi di A, grazie al -

self.MyCallbackA = MyCallbackA 

Ora, non v'è alcun riferimento al metodo MyCallbackB vincolato nel codice. È tenuto solo in una classe .__ __.__ dict__ come metodo non associato. Fondamentalmente, un metodo associato viene creato (e restituito a te) quando fai self.methodName. (AFAIK, un metodo associato funziona come una proprietà -utilizzando un descrittore (di sola lettura): almeno per le nuove classi di stile.Sono sicuro, qualcosa di simile vale a dire senza descrittori che si verificano per classi di vecchio stile. qualcuno più esperto per verificare l'affermazione relativa alle vecchie classi di stile.) Quindi, self.MyCallbackB muore non appena viene creato il weakref, perché non vi è alcun riferimento forte ad esso!

Le mie conclusioni si basano su: -

import weakref 

#Trace is called when the object is deleted! - see weakref docs. 
def trace(x): 
    print "Del MycallbackB" 

class A(object): 
    def __init__(self): 

     def MyCallbackA(): 
      print 'MyCallbackA' 
     self.MyCallbackA = MyCallbackA 
     self._testA = weakref.proxy(self.MyCallbackA) 
     print "Create MyCallbackB" 
     # To fix it, do - 
     # self.MyCallbackB = self.MyCallBackB 
     # The name on the LHS could be anything, even foo! 
     self._testB = weakref.proxy(self.MyCallbackB, trace) 
     print "Done playing with MyCallbackB" 

    def MyCallbackB(self): 
     print 'MyCallbackB' 

    def test_a(self): 
     self._testA() 

    def test_b(self): 
     self._testB() 

if __name__ == '__main__': 
    a = A() 
    #print a.__class__.__dict__["MyCallbackB"] 
    a.test_a() 

uscita

Crea MyCallbackB
Del MycallbackB
Fatto a giocare con MyCallbackB
MyCallbackA

Nota:
Ho provato a verificare questo per le classi di vecchio stile. Si è scoperto che "la stampa a.test_a .__ get__" uscite -

<method-wrapper '__get__' of instancemethod object at 0xb7d7ffcc>

sia per i vecchi e nuovi stili di classi. Quindi potrebbe non essere realmente un descrittore, solo qualcosa di descrittore. In ogni caso, il punto è che un oggetto metodo vincolato viene creato quando si accede ad un metodo di istanza tramite sé, e se non si mantiene un forte riferimento ad esso, esso verrà eliminato.

3

Le altre risposte riguardano lo perché nella domanda originale, ma non forniscono una soluzione alternativa o fanno riferimento a siti esterni.

Dopo aver lavorato su diversi altri post su StackExchange su questo argomento, molti dei quali sono contrassegnati come duplicati di questa domanda, ho finalmente trovato una soluzione alternativa. Quando conosco la natura dell'oggetto con cui ho a che fare, io uso il modulo weakref; quando invece dovrei avere a che fare con un metodo vincolato (come accade nel mio codice quando uso callback di eventi), ora utilizzo la seguente classe WeakRef come sostituto diretto di weakref.ref(). Ho provato questo con Python 2.4 fino a includere Python 2.7, ma non su Python 3.x.

class WeakRef: 

    def __init__ (self, item): 

     try: 
      self.method = weakref.ref (item.im_func) 
      self.instance = weakref.ref (item.im_self) 

     except AttributeError: 
      self.reference = weakref.ref (item) 

     else: 
      self.reference = None 


    def __call__ (self): 

     if self.reference != None: 
      return self.reference() 

     instance = self.instance() 

     if instance == None: 
      return None 

     method = self.method() 

     return getattr (instance, method.__name__) 
+1

per Python 3 sostituire '' item.im_func' con voce .__ func__' e '' item.im_self' con voce .__ self__' – lou

+0

Potrebbe essere necessario '' def __eq __ (self, Altro): restituire altro() == self() ''? È il miglior modello di confronto? (e probabilmente '' __ne__'' come l'inverso) – Rafe

Problemi correlati