2011-06-02 14 views
5

Tengo una situación en la que me gustaría mantener una asignación de un objeto a otro durante el tiempo que exista el primer objeto. Mi primer pensamiento fue usar un WeakKeyDictionary.GC ¿No elimina las referencias circulares en WeakKeyDictionaries?

import weakref 
import gc 

class M: 
    pass 

w = weakref.WeakKeyDictionary() 
m = M() 
w[m] = some_other_object 
del m 
gc.collect() 
print w.keys() 

Esto funciona bien en la mayoría de los casos. Sin embargo, en el caso en que some_other_object es (o tiene una referencia) m, la instancia M no será basura. (Para ver un ejemplo, reemplazar some_other_object con m)

Por supuesto, si Almacené mapas en el objeto en sí mismo, sería el recolector de basura cuando lo he borrado:

import weakref 
import gc 

class M: 
    pass 

m = M() 
m.circular_reference = m 
r = weakref.ref(m) 
del m 
gc.collect() 
print r 

¿Puedo conseguir los resultados del segundo ejemplo que usa el módulo weakref (es decir, sin mutar m)?

En otras palabras, ¿puedo usar el módulo weakref para asignar un objeto a sí mismo (u otro objeto que tenga una referencia fuerte) y solo mantener el objeto en memoria mientras haya otras referencias al mismo?

Respuesta

2

Realmente no tiene referencias circulares en estos ejemplos. Su círculo es como sigue:

WeakKeyDict -> valor -> Clave -> ...

Así que el dict mantiene el valor viva, que a su vez mantiene la tecla con vida, ya través de un mecanismo separado que no lo hace implica una fuerte referencia, la clave indica al dict que mantenga vivo el valor. Como no hay un círculo de referencia adecuado aquí, el GC nunca detecta ningún problema.(Sin embargo, el segundo ejemplo no contiene una referencia circular, por lo que se comporta como el primero)

Sospecho que la única solución a su problema es asegurarse de que los valores de la dict nunca tienen referencias fuertes (directa o indirecto) a cualquiera de las claves en el dict. Esto es similar a lo que propuso srgerg, pero en lugar de hacer referencias débiles a todos los valores, realmente quiere que las referencias a las claves sean débiles. Por ejemplo:

import weakref 
import gc 

class M: 
    pass 

w = weakref.WeakKeyDictionary() 
key = M() 
val = M() 
val.keyRef = weakref.ref(val) 
w[key] = val 
print len(w) ## prints 1 
del key 
gc.collect() 
print len(w) ## prints 0 

De esta manera, siempre hay fuertes referencias a los valores, pero que están controlando cuidadosamente las referencias a las claves para determinar lo que se elimina de dict. Dependiendo de la complejidad de su programa, esto podría llevar mucho tiempo implementarlo, ya que necesita rastrear manualmente todas las referencias a las claves.

Pero tal vez podemos proponer una solución más sencilla si nos más sobre el problema específico diga ..

+0

¿Podría explicar la frase "a través de un mecanismo separado que no implica una referencia fuerte"? ¿Qué es este mecanismo y por qué dices que no es una referencia fuerte? Estoy de acuerdo con su conclusión (¡aunque insatisfactoria!), Y estoy dispuesto a aceptar su respuesta, pero me gustaría aclarar este punto. – matthewwithanm

+0

El dict contiene una fuerte referencia al valor. Si se recopila la clave, se notifica al dict y se libera su referencia al valor. Entonces, esto se comporta de alguna manera como si la clave tuviera una referencia al valor (que completaría un círculo de referencia), pero en realidad no hay referencia directa. En cambio, el dict se ubica entre la clave y el valor para producir un resultado similar sin una referencia de clave-> valor. ¿Eso ayuda? Me temo que mi explicación es un poco circular (y el juego de palabras está completo ahora que la publicación se refiere a sí misma). – Luke

1

Lidiar con referencias circulares como este es probable que le haga frente, pero, impávido, intentaré responder a su pregunta de todos modos.

Un diccionario python almacena referencias tanto a sus claves como a sus valores. A WeakKeyDictionary almacena débil referencias a sus claves, pero fuerte referencias a sus valores. Por lo tanto, en su primer ejemplo, si establece w[m] = m, el diccionario w tiene una referencia débil a m como clave y una referencia fuerte a m como valor.

El módulo weakref de Python también tiene un WeakValueDictionary que tiene referencias fuertes a sus claves y referencias débiles a sus valores, pero esto no resolverá su problema.

Lo que realmente desea es un diccionario que tenga referencias débiles tanto a sus claves como a sus valores. El siguiente código hace explícitamente cada valor en el diccionario una referencia débil:

import weakref 
import gc 

class M(): 
    pass 

class Other(): 
    pass 

m = M() 
w = weakref.WeakKeyDictionary() 
w[m] = weakref.ref(m) 
print len(w.keys()) # prints 1 
del m 
print len(w.keys()) # prints 0 

m = M() 
o = Other() 
o.strong_ref = m 
w[m] = weakref.ref(o) 
print len(w.keys()) # prints 1 
del m 
print len(w.keys()) # prints 1 
del o 
print len(w.keys()) # prints 0 

Usted podría hacer que el valor de una referencia débil automáticamente por WeakKeyDictionary sub-clasificando. Sin embargo, tenga en cuenta que esto hace que no se comporte como un WeakValueDictionary real porque no tiene una devolución de llamada para notificar al diccionario cuando se ha eliminado un valor.

class MyWeakKeyValueDictionary(weakref.WeakKeyDictionary): 
    def __init__(self): 
     weakref.WeakKeyDictionary.__init__(self) 

    def __setitem__(self, key, value): 
     weakref.WeakKeyDictionary.__setitem__(self, key, weakref.ref(value)) 

w2 = MyWeakKeyValueDictionary() 
m = M() 
w2[m] = m 
print len(w2.keys()) # prints 1 
del m 
print len(w2.keys()) # prints 0 

m = M() 
o = Other() 
o.strong_ref = m 
w2[m] = o 
print len(w2.keys()) # prints 1 
del m 
print len(w2.keys()) # prints 1 
del o 
print len(w2.keys()) # prints 0 

Al final, sin embargo, me temo tratando de lidiar con referencias circulares como esto puede causar más problemas de los que vale la pena.

+0

Gracias por el esfuerzo valiente, pero que en realidad no quiero un diccionario que tiene escasas referencias al tanto de sus llaves y valores. Para ver por qué, considere el caso donde 'm' está mapeado a' (m) '. Al mapear esto al almacenar la referencia en 'm' (' m.ref = (m) ') funciona como se desee: la lista permanecerá en la memoria siempre que' m' sí, pero al eliminar 'm' se obtendrá su recolección de basura . Sin embargo, con el método que propone, nunca hay una referencia (externa) a la lista. Por lo tanto, el valor será recolectado inmediatamente. – matthewwithanm

+0

D'oh! El ejemplo en mi último comentario debería haber mapeado 'm' a' set (m) '. Verá que si hace 'w2 [m] = set (m)', el conjunto será recolectado de forma inmediata porque no hay referencias fuertes al mismo. Lo que realmente necesito es almacenar una referencia ** fuerte ** al valor (como lo haría una propiedad) pero de alguna manera hacer que el recolector de basura se de cuenta de la naturaleza circular (como lo hace para 'm.circular_reference = set (m)'). – matthewwithanm

+0

* 'set ([m])' (Lo siento, muy tarde.) – matthewwithanm

Cuestiones relacionadas