如何以及何时在Python中正确使用weakref

我有一些代码,其中类的实例有父对象之间的引用,例如:

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

认为这会创build循环引用,这样rootc1c2在Run()完成后不会被释放,对吗? 那么,怎样才能让他们获得释放呢? 我想我可以做一些像root.children.clear()self.parent = None – 但是如果我不知道该怎么做呢?

这是一个适当的时间来使用weakref模块? 什么,我确实弱化了? parent属性? children属性? 整个对象? 上述所有的? 我看到关于WeakKeyDictionary和weakref.proxy的讨论,但是在这种情况下,我不清楚它们应该如何使用。

这也是python2.4(不能升级)。

更新:示例和摘要

什么对象weakref-ify取决于哪个对象可以生存没有另一个,什么对象相互依赖。 寿命最长的物体应该包含较短寿命物体的弱点。 类似地,不应该对依赖关系进行弱化处理 – 如果是这样的话,即使仍然需要,依赖关系也可以默默地消失。

例如,如果你有一个树形结构, root ,有孩子, kids ,但可以没有孩子存在,那么root对象应该为kids使用弱回忆。 如果子对象依赖于父对象的存在,情况也是如此。 在下面,子对象需要一个父对象来计算它的深度,因此是父对象的强引用。 但是, kids属性的成员是可选的,所以使用weakRef来防止循环引用。

 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不需要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 

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

 self.children = {} 

使用:

 self.children = weakref.WeakValueDictionary() 

在你的代码中没有其他的需要改变。 这样,当一个孩子没有其他的区别时,它就会消失 – 父母的children的入口也是如此,这个children就是这个孩子的价值。

作为使用weakref模块的动机,避免参考循环与实现高速caching一样高。 参考循环不会杀了你,但他们可能会堵塞你的记忆,尤其是。 如果其中涉及实例的某些类定义了__del__ ,则会干扰gc的模块解散这些循环的能力。

我build议使用child.parent = weakref.proxy(self) 。 这是避免循环引用的一个很好的解决scheme,当parent (外部引用)的生命周期覆盖child生命周期时。 相反,当child一生覆盖parent一生时,为child使用weakref (如Alexbuild议)。 但是,如果parent双方都没有其他的生存条件,他们就永远不要使用weakref

这里用这些例子来说明这些规则。 如果将root存储在某个variables中,并将其传递给孩子,则可以使用weakref-ed parent:

 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 

如果将所有variables绑定到variables,则使用weakref-ed子项,而通过它们访问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。

你需要确保有强大的引用来保持所有的对象只要你需要它们。 这可以通过多种方式完成,例如:

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

逻辑步骤2。

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

例如,如果您在步骤1中使用[容器]方法,则仍然需要表示边缘。 节点A和B之间的边缘可以用单个参考来表示; 它可以走向任何一个方向。 再次,有许多select,例如:

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

当然,如果你在步骤1中使用了[root + children]方法,那么你的所有信息已经被充分performance出来了,所以你可以跳过这一步。

逻辑步骤3。

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

例如,如果您在步骤1中使用[容器]方法,在步骤2中使用[子项]方法,则可能希望提高某些algorithm的速度,并在每个节点与其父节点之间添加引用。 这样的信息在逻辑上是多余的,因为你可以(以性能为代价)从现有的数据中得到它。


步骤1中的所有参考文献必须很强

步骤2和步骤3中的所有参考可能很弱或很强 。 使用强引用没有好处。 使用弱引用有一个优点,直到你知道周期不再可能。 严格来说,一旦你知道周期是不可能的,那么使用弱的还是强的参考是没有区别的。 但为了避免考虑这个问题,你也可以在步骤2和步骤3中只使用弱引用。