Как и когда правильно использовать слабый реф в Python - PullRequest
48 голосов
/ 02 октября 2009

У меня есть некоторый код, где экземпляры классов имеют родительские <-> дочерние ссылки друг на друга, например ::

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 - но что, если я не знаю, когда это сделать?

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

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

Обновление: пример и резюме

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

Если, например, у вас есть древовидная структура root, которая имеет дочерние элементы kids, но может существовать без дочерних элементов, тогда объект root должен использовать слабые ссылки для kids. Это также имеет место, если дочерний объект зависит от существования родительского объекта. Ниже для дочернего объекта требуется родительский объект, чтобы вычислить его глубину, следовательно, строгий 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 s просто предоставляют способ уведомить Facade 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

Ответы [ 3 ]

29 голосов
/ 02 октября 2009

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

self.children = {}

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

self.children = weakref.WeakValueDictionary()

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

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

18 голосов
/ 02 октября 2009

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

Здесь эти правила иллюстрируются примерами. Используйте слабый родительский элемент, если вы храните 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

Используйте потомков со слабой привязкой, если вы связываете их все с переменными, а через них осуществляется доступ к 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
1 голос
/ 23 марта 2012

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

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

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

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

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

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

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

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

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

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

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

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


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

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

...