2009-10-02 14 views
42

tengo algo de código en la que las instancias de clases tienen padres < - referencias> niño entre sí, por ejemplo:¿Cómo y cuándo utilizar adecuadamente weakref en 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() 

I que esto crea referencias circulares de tal manera que root, c1 y c2 no se liberarán después de que se complete Run(), ¿verdad ?. Entonces, ¿cómo lograr que sean liberados? Creo que puedo hacer algo como root.children.clear() o self.parent = None, pero ¿y si no sé cuándo hacerlo?

¿Es este el momento apropiado para usar el módulo weakref? ¿Qué, exactamente, defiendo débilmente? el atributo parent? El atributo children? El objeto completo? Todas las anteriores? Veo hablar sobre WeakKeyDictionary y weakref.proxy, pero no tengo claro cómo deberían usarse, si es que lo hacen, en este caso.

Esto también está en python2.4 (no se puede actualizar).

Actualización: Ejemplo y resumen

Lo que se opone a weakref-ify depende de qué objeto se puede vivir sin el otro, y qué objetos dependen unos de otros. El objeto que vive más tiempo debe contener elementos débiles para los objetos de vida más corta. Del mismo modo, las revisiones débiles no deberían hacerse a las dependencias; si lo son, la dependencia podría desaparecer silenciosamente aunque todavía se necesite.

Si, por ejemplo, que tienen una estructura de árbol, root, que tiene hijos, kids, pero puede existir sin hijos, entonces el objeto root debe utilizar weakrefs por su kids. Este es también el caso si el objeto secundario depende de la existencia del objeto principal. A continuación, el objeto secundario requiere un padre para calcular su profundidad, de ahí la referencia fuerte para parent. Sin embargo, los miembros del atributo kids son opcionales, por lo que las referencias débiles se utilizan para evitar una referencia circular.

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. 

Para darle la vuelta a la relación, tenemos algo como lo que se muestra a continuación. Aquí, las clases Facade requieren una instancia Subsystem para funcionar, por lo que usan una referencia fuerte para el subsistema que necesitan. Subsystem s, sin embargo, no requieren un Facade para funcionar. Subsystem s solo proporciona una forma de notificar Facade s acerca de las acciones de los demá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 

Respuesta

25

Sí, weakref's excelente aquí. En concreto, en lugar de:

self.children = {} 

uso:

self.children = weakref.WeakValueDictionary() 

Nada más necesita cambiar en el código. De esta forma, cuando un niño no tiene otras diferencias, simplemente desaparece, y también lo hace la entrada en el mapa del padre children que tiene ese niño como el valor.

Evitar los bucles de referencia está a la altura de las cachés de implementación como una motivación para usar el módulo weakref. Los bucles de referencia no te matarán, pero pueden terminar obstruyendo tu memoria, especialmente.si algunas de las clases cuyas instancias están involucradas en ellos definen __del__, ya que eso interfiere con la capacidad del módulo gc de disolver esos bucles.

+0

Además, si está seguro de que no necesitará el gc cíclico, puede desactivarlo para un pequeño aumento del rendimiento . –

+1

Gracias, Alex. ¿Hay alguna razón específica para rechazar los "niños" en lugar de "padres"? ¿El efecto sería el mismo? ¿Qué pasaría si 'parent 'fuera débil también? En el caso de una lista de doble enlace, ¿deberían 'prev ',' next', o ambos ser weakrefs? –

+5

Esta es una mala sugerencia. Todos los niños del ejemplo serán destruidos justo después de regresar de 'Ejecutar()'. En general, casi siempre vincula una raíz de estructura a variable, por lo que la forma correcta es usar 'weakref' para' parent', pero no 'children'. –

13

Sugiero usar child.parent = weakref.proxy(self). Esta es una buena solución para evitar referencias circulares en caso de que la vida útil de (referencias externas a) parent cubra la vida útil de child. Contrariamente, use weakref para child (como sugirió Alex) cuando la vida útil de child cubra la vida útil de parent. Pero nunca use weakref cuando ambos parent y child pueden estar vivos sin otro.

Aquí se ilustran estas reglas con ejemplos. Utilice los padres weakref-ed si almacena la raíz de alguna variable y lo pasa alrededor, mientras que los niños se accede desde ella:

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 

preparadas para niños weakref-ed si enlaza todos ellos a las variables, mientras que la raíz se accede a través de ellos :

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

Pero tampoco va a trabajar para lo siguiente:

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

Hay muchos casos en que uno o ambos hijos y padres pueden estar vivos sin el otro, siempre y cuando otras entidades todavía tengan referencias a ellos_; en ese momento es posible que desee utilizar referencias mutuamente débiles (esas otras referencias externas harán el trabajo de mantener las entidades con vida exactamente el tiempo que sea necesario). –

+2

** "Sugiero usar' child.parent = weakref.proxy (self) '." ** _Thisss._ Este es el enfoque canónico para el caso común de un 'padre' de larga duración que contiene 'múltiples hijos de corta duración ' '-ren. La solución de Alexis es más aplicable al caso límite de múltiples hijos de larga duración que posean un "padre" efímero, que rara vez veo en libertad. –

1

quería aclarar qué referencias pueden ser débiles. El siguiente enfoque es general, pero utilizo el árbol doblemente vinculado en todos los ejemplos.

lógica Paso 1.

Usted necesita asegurarse de que hay fuertes referencias a tener todos los objetos vivos, siempre y cuando los necesite. Se puede hacer de muchas maneras, por ejemplo por:

  • [nombres directos]: una referencia con nombre a cada nodo en el árbol
  • [recipiente]: una referencia a un contenedor que almacena todos los nodos
  • [+ niños root]: una referencia al nodo raíz, y las referencias de cada nodo a sus niños
  • [hojas + padre]: las referencias a todos los nodos hoja, y las referencias de cada nodo a su matriz

Paso lógico 2.

Ahora agrega referencias para representar información, si es necesario.

Por ejemplo, si usó el enfoque [contenedor] en el Paso 1, aún debe representar los bordes. Un borde entre los nodos A y B se puede representar con una sola referencia; puede ir en cualquier dirección. De nuevo, hay muchas opciones, por ejemplo:

  • [niños]: referencias de cada nodo a sus niños
  • [parent]: una referencia desde cada nodo a su matriz
  • [conjunto de conjuntos] : un conjunto que contiene conjuntos de 2 elementos; cada elemento 2 contiene referencias a nodos de un borde

Por supuesto, si usó el enfoque [root + children] en el Paso 1, toda su información ya está completamente representada, por lo que omita este paso.

lógica Paso 3.

Ahora se añaden referencias para mejorar el rendimiento, si se desea.

Por ejemplo, si utilizó el enfoque [contenedor] en el Paso 1, y [niños] se acercan en el Paso 2, es posible que desee mejorar la velocidad de ciertos algoritmos y agregar referencias entre cada nodo y su elemento primario. Dicha información es lógicamente redundante, ya que podría (a un costo en rendimiento) derivarla de los datos existentes.


todas las referencias en el Paso 1 deben ser fuertes.

Todas las referencias en los pasos 2 y 3 pueden ser débiles o fuertes. No hay ninguna ventaja al usar referencias fuertes. Existe una ventaja al usar referencias débiles hasta que sepa que los ciclos ya no son posibles. Estrictamente hablando, una vez que sepa que los ciclos son imposibles, no importa si usar referencias débiles o fuertes. Pero para evitar pensar en ello, también podría utilizar referencias exclusivamente débiles en los Pasos 2 y 3.

Cuestiones relacionadas