2009-03-01 3 views
9

Ich habe ein Projekt, in dem ich versuche, schwache Referenzen mit Rückrufen zu verwenden, und ich verstehe nicht, was ich falsch mache. Ich habe einen vereinfachten Test erstellt, der das genaue Verhalten zeigt, mit dem ich verwirrt bin.Warum arbeitet der weakref nicht an dieser gebundenen Methode?

Warum funktioniert test_a in diesem Test wie erwartet, aber der weakref für self.MyCallbackB verschwindet zwischen der Klasseninitialisierung und dem Aufruf von test_b? Ich dachte, solange die Instanz (a) existiert, sollte der Verweis auf self.MyCallbackB existieren, tut es aber nicht.

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() 

Antwort

10

Sie möchten eine WeakMethod.

Eine Erklärung, warum Ihre Lösung funktioniert nicht in der Diskussion über das Rezept gefunden werden kann:

Normalen weakref.refs zu gebundenen Methoden, um die Art und Weise nicht ganz funktionieren erwartet man, weil gebundene Methoden erstklassige Objekte; weakrefs zu gebundenen Methoden sind dead-on-arrival, es sei denn, ein anderer starker Verweis auf die gleiche gebundene Methode existiert.

4

Nach der Dokumentation für das WeakRef Modul:

Im Folgenden bedeutet der Begriff referent das Objekt, das zu durch eine schwache Referenz bezeichnet wird.

Ein schwacher Verweis auf ein Objekt ist nicht genug, um das Objekt am Leben zu halten: wenn der einzigen verbleibenden Verweis auf einen referenten sind schwache Verweise, Müll Sammlung den referenten zu zerstören frei ist und seine Erinnerung wieder verwendet für etwas anderes.

Was ist mit MyCallbackA passiert ist, dass Sie in den Instanzen A, dank einen Verweis darauf halten -

self.MyCallbackA = MyCallbackA 

Jetzt gibt es keinen Hinweis auf die gebundene Methode MyCallbackB in Ihrem Code. Es wird nur in einer .__ Klasse __.__ dict__ als ungebundene Methode gehalten. Im Grunde wird eine gebundene Methode erstellt und zurückgegeben, wenn Sie self.methodName ausführen. (AFAIK, eine gebundene Methode funktioniert wie eine Eigenschaft - unter Verwendung eines Deskriptors (schreibgeschützt): zumindest für neue Stilklassen. Ich bin mir sicher, etwas Ähnliches, dh ohne Deskriptoren passiert für alte Stilklassen. Ich überlasse es Jemand, der erfahrener ist, um die Behauptung über alte Stilklassen zu überprüfen.) So stirbt self.MyCallbackB, sobald die Schwachstelle erzeugt wird, weil es keinen starken Bezug darauf gibt!

sind meine Schlussfolgerungen basieren auf: -

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() 

Ausgabe

erstellen MyCallbackB
Del MycallbackB

MyCallbackA

0 mit MyCallbackB Geschehen spielen

Hinweis:
Ich habe versucht, dies für alte Klassen zu überprüfen. Es stellte sich heraus, dass "Druck a.test_a .__ get__" Ausgänge -

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

für den neuen und alten Stil-Klassen. Es kann also nicht wirklich ein Deskriptor sein, nur ein Deskriptor. In jedem Fall besteht der Punkt darin, dass ein gebundenes Methodenobjekt erstellt wird, wenn Sie eine Instanzmethode über self aufrufen, und es wird gelöscht, sofern Sie nicht einen starken Verweis darauf beibehalten.

3

Die anderen Antworten adressieren die warum in der ursprünglichen Frage, aber beide bieten keine Abhilfe oder verweisen auf externe Websites.

Nachdem ich in diesem Thema mehrere andere Beiträge zu StackExchange durchgearbeitet habe, von denen viele als Duplikate dieser Frage markiert sind, kam ich schließlich zu einer kurzen Problemumgehung. Wenn ich die Art des Objekts kenne, mit dem ich es zu tun habe, benutze ich das weakref-Modul; Wenn ich mich stattdessen mit einer gebundenen Methode beschäftigen sollte (wie es in meinem Code bei der Verwendung von Event-Callbacks vorkommt), verwende ich jetzt die folgende WeakRef-Klasse als direkten Ersatz für weakref.ref(). Ich habe dies mit Python 2.4 durch und einschließlich Python 2.7, aber nicht mit Python 3.x getestet.

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

für Python 3 ersetzen 'item.im_func' mit' Artikel .__ func__' und 'item.im_self' mit' Artikel .__ self__' – lou

+0

Möglicherweise müssen '' def __eq __ (self, andere): return andere() == self() ''? Ist das das beste Vergleichsmuster? (und wahrscheinlich '' __ne__'' als Inverse) – Rafe

Verwandte Themen