Как и когда правильно использовать weakref в Python

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

class Node:

    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, но мне не ясно, как их следует использовать, если вообще нужно, в этом случае.

Это также на Python 2.4 (не может быть обновлено).

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

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

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

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

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()
cli = Facade(sub)

person Community    schedule 02.10.2009    source источник


Ответы (3)


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

self.children = {}

использовать:

self.children = weakref.WeakValueDictionary()

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

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

person Alex Martelli    schedule 02.10.2009
comment
Кроме того, если вы уверены, что вам не понадобится циклический сборщик мусора, вы можете отключить его для небольшого увеличения производительности. - person John La Rooy; 02.10.2009
comment
Спасибо, Алекс. Есть ли конкретная причина для weakref children, а не parent? Будет ли эффект таким же? Что произойдет, если parent тоже будет слаборефлексированным? В случае двусвязного списка должны ли prev, next или оба быть слабыми ссылками? - person Richard Levasseur; 02.10.2009
comment
Это плохое предложение. Все дочерние элементы в примере будут уничтожены сразу после возврата из Run(). В общем, вы почти всегда привязываете корень структуры к переменной, поэтому правильно использовать weakref вместо parent, но не children. - person Denis Otkidach; 02.10.2009
comment
Я также нашел это сообщение, в котором рекомендуются слабые ссылки к корню, а не к листьям: http://74.125.155.132/search?q=cache:http://mail.python.org/pipermail/python-list/2009-март/705913.html - person Richard Levasseur; 02.10.2009
comment
Дело не в родительском или дочернем, а в том, какое из них может существовать без другого. В этом игрушечном примере мало указаний, за исключением того, что мы знаем, что родитель может существовать без дочерних элементов (потому что он начинается таким образом!), но мы не знаем, будет ли обратное верно - возможно, дети по своей сути нуждаются в некоторых услугах от родителя, и если это так, они должны ссылаться на нормальную ссылку или сильную ссылку на нее. @Denis, конечно, все (родительский И дочерний) уничтожается в конце Run в этом игрушечном примере - черт возьми: это то, что делает его игрушечным, и не отменяет моего предложения. - person Alex Martelli; 02.10.2009
comment
Спасибо. Я добавил пример/резюме, который объединяет ваши ответы и ответы Дениса. Я также пометил это как вики сообщества на случай, если я что-то неправильно понял. - person Richard Levasseur; 06.10.2009

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

Используйте родителя со слабой ссылкой, если вы привязываете корень к имени и передаете его, в то время как дочерние элементы доступны из него:

def Run():
    root, c1, c2 = Node(), Node(), Node()
    root.AddChild('first', c1)
    root.AddChild('second', c2)
    return root  # 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
person Denis Otkidach    schedule 02.10.2009
comment
Существует множество случаев, когда один или оба из дочернего и родительского элементов могут существовать без другого до тех пор, пока другие объекты еще содержат ссылки на них; именно тогда вы можете захотеть использовать взаимные слабые ссылки (эти другие внешние ссылки будут выполнять работу по поддержанию существования сущностей ровно столько, сколько необходимо). - person Alex Martelli; 02.10.2009
comment
Я предлагаю использовать child.parent = weakref.proxy(self). Thisss. Это канонический подход для общего случая, когда один долгоживущий parent содержит несколько короткоживущих child-ren. Решение Алексис более применимо к крайнему случаю, когда несколько долгоживущих child-ренов владеют одним недолговечным parent, что я редко вижу в дикой природе. - person Cecil Curry; 29.01.2016

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

Логичный шаг 1.

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

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

Логичный шаг 2.

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

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

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

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

Логичный шаг 3.

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

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


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

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

person Community    schedule 23.03.2012