Comment et quand utiliser de manière appropriée weakref en Python

J'ai du code où les instances de classes ont des références parentenfant les unes aux autres, par exemple:

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

, je penser cela crée des références circulaires telles que root, c1 et c2 ne sera pas libéré après Run() est terminée, non?. Alors, comment les amener à être libéré? Je pense que je peux faire quelque chose comme root.children.clear(), ou self.parent = None - mais si je ne sais pas quand le faire?

Est - ce le moment approprié pour utiliser le module weakref? Qu'est-ce que, exactement, Est-ce que je weakref'ify? le parent attribut? L'attribut children? L'ensemble de l'objet? Tous les ci-dessus? Je vois parler du WeakKeyDictionary et weakref.proxy, mais il n'est pas clair pour moi comment ils doivent être utilisés, le cas échéant, dans ce cas.

C'est aussi sur python2 .4 (impossible de mettre à jour).

Mise à jour: exemple et résumé

Quels objets à weakrefy dépendent de quel objet peut vivre sans l'autre, et quels objets dépendent les uns des autres. L'objet qui vit le plus longtemps devrait contient weakrefs aux objets à durée de vie plus courte. De même, weakrefs ne devrait pas être fait en dépendances-si elles le sont, la dépendance pourrait disparaître silencieusement même si elle est toujours nécessaire.

Si, par exemple, vous avez une structure arborescente, root, qui a des enfants, kids, mais peut exister sans enfants, alors l'objet root doit utiliser weakrefs pour son kids. C'est également le cas si l'enfant de l'objet dépend de l'existence de l'objet parent. Ci-dessous, l'objet enfant nécessite {[26] } un parent pour calculer sa profondeur, d'où le fort-ref pour parent. Les membres de l'attribut kids sont facultatifs, donc weakrefs sont utilisés pour empêcher une référence circulaire.

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.

Pour inverser la relation, nous avons quelque chose comme ci-dessous. Ici, les classes Facade nécessitent une instance Subsystem pour fonctionner, elles utilisent donc une référence forte au sous-système dont elles ont besoin. Subsystem S, cependant, ne nécessitent pas de Facade pour fonctionner. Subsystem S juste fournir un moyen de notifier FacadeS sur les actions de l'autre.

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
42
demandé sur Richard Levasseur 2009-10-02 07:03:40

3 réponses

Oui, weakref est excellent ici. Plus précisément, au lieu de:

self.children = {}

Utilisation:

self.children = weakref.WeakValueDictionary()

Rien d'autre n'a besoin de changement dans votre code. De cette façon, quand un enfant n'a pas d'autres différences, il disparaît simplement-et l'entrée dans la carte children du parent qui a cet enfant comme valeur.

Éviter les boucles de référence est à la hauteur de l'implémentation des caches comme motivation pour utiliser le module weakref. Les boucles Ref ne vous tueront pas, mais elles peuvent finir par obstruer votre mémoire, esp. si certaines des classes dont les instances sont impliquées définissent __del__, car cela interfère avec la capacité du module gc à dissoudre ces boucles.

27
répondu Alex Martelli 2009-10-02 03:10:11

Je suggère d'utiliser child.parent = weakref.proxy(self). C'est une bonne solution pour éviter les références circulaires dans le cas où la durée de vie de (références externes à) parent couvre la durée de vie de child. Au contraire, utilisez weakref pour child (comme Alex l'a suggéré) lorsque la durée de vie de child couvre la durée de vie de parent. Mais n'utilisez jamais weakref lorsque parent et child peuvent être vivants sans autre.

Ici, ces règles sont illustrées par des exemples. Utilisez le parent weakref-ed si vous stockez la racine dans une variable et la transmettez, alors que les enfants sont accès à partir de celui-ci:

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

Utilisez les enfants weakref-ed si vous les liez tous aux variables, alors que root est accessible à travers eux:

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

Mais aucun ne fonctionnera pour ce qui suit:

def Run():
  root, c1, c2 = Node(), Node(), Node()
  root.AddChild("first", c1)
  root.AddChild("second", c2)
  return c1
13
répondu Denis Otkidach 2009-10-05 16:12:55

Je voulais clarifier quelles références peuvent être faibles. L'approche suivante est générale, mais j'utilise l'arbre doublement lié dans tous les exemples.

Étape Logique 1.

Vous devez vous assurer qu'il existe des références fortes pour garder tous les objets en vie aussi longtemps que vous en avez besoin. Il peut être fait de plusieurs façons, par exemple:

  • [noms directs]: une référence nommée à chaque nœud de l'arborescence
  • [container]: une référence à un conteneur qui stocke toutes les nœuds
  • [racine + enfants]: une référence au nœud racine, et des références de chaque nœud à ses enfants
  • [leaves + parent]: références à tous les nœuds feuille, et références de chaque nœud à son parent

Étape Logique 2.

Maintenant, vous ajoutez des références pour représenter des informations, si nécessaire.

Par exemple, si vous avez utilisé l'approche [container] à L'Étape 1, vous devez toujours représenter les bords. Un bord entre les nœuds A et B peut être représenté avec un seul référence; il peut aller dans les deux sens. Encore une fois, il y a beaucoup d'options, par exemple:

  • [les enfants]: références de chaque nœud à ses enfants
  • [parent]: une référence de chaque nœud à son parent
  • [ensemble d'ensembles]: un ensemble contenant des ensembles de 2 éléments; chaque élément 2 contient des références à des nœuds d'un bord

Bien sûr, si vous avez utilisé l'approche [root + children] à L'Étape 1, toutes vos informations sont déjà entièrement représentées, donc vous ignorez ceci étape.

Étape Logique 3.

Maintenant, vous ajoutez des références pour améliorer les performances, si vous le souhaitez.

Par exemple, si vous avez utilisé l'approche [container] à L'Étape 1 et l'approche [children] à L'Étape 2, Vous pouvez souhaiter améliorer la vitesse de certains algorithmes et ajouter des références entre chaque nœud et son parent. De telles informations sont logiquement redondantes, puisque vous pourriez (à un coût en performance) les dériver des données existantes.


Toutes les références de L'Étape 1 doivent soyez fort .

Toutes les références des étapes 2 et 3 peuvent être faibles ou fortes . Il n'y a aucun avantage à utiliser des références fortes. Il y a un avantage à utiliser des références faibles jusqu'à ce que vous sachiez que les cycles ne sont plus possibles. Strictement parlant, une fois que vous savez que les cycles sont impossibles, cela ne fait aucune différence d'utiliser des références faibles ou fortes. Mais pour éviter d'y penser, autant utiliser exclusivement des références faibles dans les étapes 2 et 3.

1
répondu max 2012-03-23 05:48:28