Подтвердить что ты не робот

Как и когда надлежащим образом использовать weakref в 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()

Я думаю, что это создает круговые ссылки, так что root, c1 и c2 не будут освобождены после завершения Run(), правильно?. Итак, как их освободить? Я думаю, что могу сделать что-то вроде root.children.clear() или self.parent = None - но что, если я не знаю, когда это сделать?

Это подходящее время для использования модуля weakref? Что именно, я слабо понимаю? атрибут parent? Атрибут children? Весь объект? Все вышеперечисленное? Я вижу разговоры о WeakKeyDictionary и weakref.proxy, но неясно, как их следует использовать, если это вообще возможно.

Это также на python2.4 (невозможно обновить).

Обновление: пример и сводка

Какие объекты для weakref-ify зависят от того, какой объект может жить без другого, а какие объекты зависят друг от друга. Объект, который живет дольше всех, должен содержать слабые ссылки на объекты с более коротким периодом. Аналогичным образом, слабые стороны не должны быть связаны с зависимостями - если они есть, зависимость может исчезнуть молча, даже если она по-прежнему необходима.

Если, например, у вас есть древовидная структура, root, у которой есть дети, kids, но может существовать без детей, тогда объект root должен использовать weakrefs для своего kids. Это также имеет место, если дочерний объект зависит от существования родительского объекта. Ниже для дочернего объекта требуется родительский элемент, чтобы вычислить его глубину, и, следовательно, strong-ref для parent. Элементы атрибута kids необязательны, поэтому для предотвращения циклической ссылки используются слабые ссылки.

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.

Чтобы перевернуть отношения вокруг, у нас есть что-то вроде ниже. Здесь для классов Facade требуется, чтобы экземпляр Subsystem работал, поэтому они используют сильную ссылку на подсистему, в которой они нуждаются. Subsystem s, однако, не требует работы Facade. Subsystem просто укажите способ уведомления Facade о действиях друг друга.

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
4b9b3361

Ответ 1

Да, слабый здесь отлично. В частности, вместо:

self.children = {}

использование:

self.children = weakref.WeakValueDictionary()

Ничто не нуждается в изменении вашего кода. Таким образом, когда у ребенка нет других различий, он просто уходит - и поэтому запись в родительской карте children, которая имеет этот ребенок как значение.

Избегание опорных циклов высоко оценивается с использованием кэшей в качестве мотивации для использования модуля weakref. Петли Ref не убьют вас, но они могут в конечном итоге засорить вашу память, особенно. если некоторые из классов, в которых задействованы экземпляры, определяют __del__, поскольку это мешает модулю gc для растворения этих циклов.

Ответ 2

Я предлагаю использовать child.parent = weakref.proxy(self). Это хорошее решение, чтобы избежать циклических ссылок в случае, когда время жизни (внешние ссылки на) parent охватывает время жизни child. Вопреки, используйте weakref для child (как предложил Алекс), когда время жизни child охватывает время жизни parent. Но никогда не используйте weakref, когда оба parent и child могут быть живы без других.

Здесь эти правила проиллюстрированы примерами. Используйте weakref-ed parent, если вы храните root в некоторой переменной и передаете его, в то время как к ним обращаются дети:

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

Используйте weakref-ed children, если вы привязываете все их к переменным, а через них можно получить доступ через root:

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

Но ни одно из них не будет работать для следующего:

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

Ответ 3

Я хотел уточнить, какие ссылки могут быть слабыми. Следующий подход является общим, но я использую двусвязное дерево во всех примерах.

Логический шаг 1.

Вам нужно убедиться, что есть убедительные ссылки, чтобы сохранить все объекты в живых до тех пор, пока они вам понадобятся. Это можно сделать разными способами, например:

  • [direct names]: именованная ссылка для каждого node в дереве
  • [container]: ссылка на контейнер, который хранит все узлы
  • [root + children]: ссылка на корень node и ссылки от каждого node на его дочерние элементы
  • [leaves + parent]: ссылки на все листовые узлы и ссылки из каждого node на родительский

Логический шаг 2.

Теперь вы добавляете ссылки для представления информации, если это необходимо.

Например, если вы использовали [контейнер] подход на шаге 1, вам все равно придется представлять ребра. Ребро между узлами А и В может быть представлено с одной ссылкой; он может идти в любом направлении. Опять же, есть много вариантов, например:

  • [children]: ссылки от каждого node на его дочерние элементы
  • [parent]: ссылка от каждого node на родителя
  • [набор множеств]: набор, содержащий 2-элементные множества; каждый 2-элемент содержит ссылки на узлы одного ребра

Конечно, если вы использовали подход [root + children] на шаге 1, вся ваша информация уже полностью представлена, поэтому вы пропустите этот шаг.

Логический шаг 3.

Теперь вы добавляете ссылки для повышения производительности, если это необходимо.

Например, если вы использовали подход [container] на шаге 1 и [дети] на шаге 2, вам может потребоваться улучшить скорость определенных алгоритмов и добавить ссылки между каждым node и его родителем. Такая информация является логически избыточной, поскольку вы можете (по стоимости в производительности) извлечь ее из существующих данных.


Все ссылки на шаге 1 должны быть сильными.

Все ссылки на шагах 2 и 3 могут быть слабыми или сильными. Нет смысла использовать сильные ссылки. Существует преимущество использования слабых ссылок, пока вы не узнаете, что циклы больше невозможны. Строго говоря, как только вы знаете, что циклы невозможны, не имеет значения, использовать ли слабые или сильные ссылки. Но чтобы не думать об этом, вы могли бы использовать исключительно слабые ссылки на шагах 2 и 3.