2009-03-01 15 views
9

Tengo un proyecto en el que trato de usar weakrefs con devoluciones de llamada, y no entiendo lo que estoy haciendo mal. Creé una prueba simplificada que muestra el comportamiento exacto con el que estoy confundido.¿Por qué el weakref no funciona en este método enlazado?

¿Por qué es que en esta prueba test_a funciona como se esperaba, pero el weakref para self.MyCallbackB desaparece entre la inicialización de la clase y la llamada test_b? Pensé que, mientras exista la instancia (a), la referencia a self.MyCallbackB debería existir, pero no es así.

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

Respuesta

10

Quiere WeakMethod.

Una explicación de por qué su solución no funciona se puede encontrar en la discusión de la receta:

weakref.refs normales a métodos encuadernados que no termina de funcionar de la manera que uno espera, porque los métodos son encuadernados objetos de primera clase; Las réplicas débiles de los métodos enlazados son "dead-on-arrival" a menos que exista alguna otra referencia fuerte al mismo método enlazado.

4

De acuerdo con la documentación para el módulo weakref:

En lo que sigue, el término referente significa que el objeto que se hace referencia por una referencia débil.

Una referencia débil para un objeto no es suficiente para mantener el objeto vivo: cuando los que quedan referencias a un referente son referencias débiles, basura colección es libre para destruir el referente y volver a utilizar su memoria para algo más.

Qué está sucediendo con MyCallbackA es que usted está sosteniendo una referencia a ella en los casos de A, gracias a -

self.MyCallbackA = MyCallbackA 

Ahora, no hay ninguna referencia a la MyCallbackB método vinculado en el código. Se mantiene solo en .__ clase __.__ dict__ como método independiente. Básicamente, se crea un método vinculado (y se le devuelve) cuando lo hace self.methodName. (AFAIK, un método enlazado funciona como una propiedad -utilizando un descriptor (solo lectura): al menos para las nuevas clases de estilo. Estoy seguro de que algo similar, es decir, sin descripciones, sucede para las clases de estilo antiguo. Lo dejo a alguien con más experiencia para verificar la afirmación sobre las clases de estilo antiguo). Por lo tanto, self.MyCallbackB muere tan pronto como se crea el weakref, ¡porque no hay una referencia fuerte para él!

Mis conclusiones se basan en: -

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

salida

Crear MyCallbackB
Del MycallbackB
terminado de jugar con MyCallbackB
MyCallbackA

Nota:
Intenté verificar esto para las clases de estilo anteriores. Resultó que "a.test_a de impresión .__ get__" salidas -

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

para los nuevos y viejos clases de estilo. Por lo tanto, puede que no sea realmente un descriptor, simplemente algo similar a un descriptor. En cualquier caso, el punto es que un objeto de método vinculado se crea cuando accedes a un método de instancia a través de uno mismo, y a menos que mantengas una referencia fuerte al mismo, se eliminará.

3

Las demás respuestas se dirigen a por qué en la pregunta original, pero o bien no proporcionan una solución alternativa o se refieren a sitios externos.

Después de trabajar a través de varias otras publicaciones en StackExchange sobre este tema, muchas de las cuales están marcadas como duplicados de esta pregunta, finalmente llegué a una solución sucinta. Cuando conozco la naturaleza del objeto con el que estoy tratando, uso el módulo weakref; cuando podría estar tratando con un método enlazado (como ocurre en mi código cuando uso devoluciones de llamada de eventos), ahora uso la siguiente clase WeakRef como reemplazo directo para weakref.ref(). He probado esto con Python 2.4 e incluyendo Python 2.7, pero no en 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

para Python 3 sustituir '' item.im_func' con elemento .__ func__' y '' item.im_self' con elemento .__ self__' – lou

+0

Puede ser que necesite '' def __eq __ (self, otro): devuelve otros() == self() ''? ¿Es ese el mejor patrón de comparación? (y probablemente '' __ne__'' como el inverso) – Rafe

Cuestiones relacionadas