57

我有一些代码,其中类的实例具有彼此的父<->子引用,例如:

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

认为这会创建循环引用rootc1并且c2在 Run() 完成后不会被释放,对吧?那么,如何让他们获得自由呢?我想我可以做类似的事情root.children.clear(),或者self.parent = None- 但是如果我不知道什么时候做呢?

现在是使用weakref 模块的合适时机吗?我究竟要弱化什么?属性parentchildren属性?整个对象?上述所有的?我看到有关 WeakKeyDictionary 和weakref.proxy 的讨论,但我不清楚在这种情况下应该如何使用它们(如果有的话)。

这也在 Python 2.4 上(无法升级)。

更新:示例和摘要

对哪些对象进行弱引用取决于哪些对象可以在没有另一个的情况下生存,以及哪些对象相互依赖。寿命最长的对象应该包含对寿命较短的对象的弱引用。类似地,不应该对依赖项进行弱引用 - 如果是,则即使仍然需要依赖项,也可能会默默地消失。

例如,如果您有一个树结构 ,root有孩子 ,kids但可以没有孩子而存在,那么该root对象应该为其 . 使用弱引用kids。如果子对象依赖于父对象的存在,情况也是如此。下面,子对象需要一个父对象才能计算其深度,因此为parent. 不过,该kids属性的成员是可选的,因此使用了weakrefs 来防止循环引用。

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实例才能工作,因此它们对所需的子系统使用强引用。 Subsystem但是,s 不需要 aFacade来工作。 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)
4

3 回答 3

33

是的,weakref 在这里很棒。具体来说,而不是:

self.children = {}

采用:

self.children = weakref.WeakValueDictionary()

您的代码中没有其他需要更改的地方。这样,当一个孩子没有其他差异时,它就会消失 - 父children映射中以该孩子为值的条目也是如此。

避免引用循环与实现缓存作为使用weakref模块的动机相当。参考循环不会杀死你,但它们最终可能会堵塞你的记忆,尤其是。如果其中涉及其实例的某些类定义了__del__,因为这会干扰gc的模块解决这些循环的能力。

于 2009-10-02T03:10:11.420 回答
21

我建议使用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

如果您将每个孩子绑定到一个名称并传递它们,则使用弱引用的孩子,同时从它们访问根:

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
于 2009-10-02T08:17:00.477 回答
1

我想澄清哪些引用可能很弱。以下方法是通用的,但我在所有示例中都使用了双向链接树。

逻辑步骤 1。

您需要确保有强引用来保持所有对象在需要时保持活动状态。它可以通过多种方式完成,例如:

  • [直接名称]:对树中每个节点的命名引用
  • [container]:对存储所有节点的容器的引用
  • [root + children]:对根节点的引用,以及每个节点对其子节点的引用
  • [leaves + parent]:对所有叶子节点的引用,以及每个节点对其父节点的引用

逻辑步骤 2。

现在,如果需要,您可以添加引用来表示信息。

例如,如果您在步骤 1 中使用 [容器] 方法,您仍然必须表示边缘。节点 A 和 B 之间的边可以用单个参考表示;它可以朝任何一个方向发展。同样,有很多选择,例如:

  • [children]:每个节点对其子节点的引用
  • [parent]:从每个节点到其父节点的引用
  • [set of sets]:包含 2 元素集合的集合;每个 2 元素包含对一条边的节点的引用

当然,如果您在步骤 1 中使用了 [root + children] 方法,那么您的所有信息都已经完全表示,因此您可以跳过此步骤。

逻辑步骤 3。

现在,如果需要,您可以添加引用以提高性能。

例如,如果您在步骤 1 中使用 [container] 方法,在步骤 2 中使用 [children] 方法,您可能希望提高某些算法的速度,并在每个节点及其父节点之间添加引用。此类信息在逻辑上是多余的,因为您可以(以性能为代价)从现有数据中获取它。


步骤 1 中的所有引用都必须是 strong

步骤 2 和 3 中的所有引用可能是 weak 或 strong。使用强引用没有任何优势。在您知道循环不再可能之前,使用弱引用是有好处的。严格来说,一旦你知道循环是不可能的,使用弱引用还是强引用都没有区别。但是为了避免考虑它,您不妨在第 2 步和第 3 步中只使用弱引用。

于 2012-03-23T05:48:28.270 回答