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

时间:2009-10-02 03:03:41

标签: python circular-reference

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

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

认为这会创建循环引用,以便在Run()完成后不会释放rootc1c2,对吧?那么,如何让它们被释放?我想我可以做root.children.clear()self.parent = None之类的事情 - 但如果我不知道该怎么做呢?

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

这也是在python2.4上(无法升级)。

更新:示例和摘要

weakref-ify的哪些对象取决于哪个对象可以在没有另一个对象的情况下生存,以及哪些对象相互依赖。生命时间最长的对象应包含较短寿命对象的弱化参数。类似地,不应该将weakrefs设置为依赖项 - 如果它们是依赖项,依赖项可能会默默地消失,即使它仍然需要。

例如,如果您的树结构root包含子项kids,但没有子项,那么root对象应该为其kids使用weakrefs。如果子对象依赖于父对象的存在,情况也是如此。下面,子对象需要父级才能计算其深度,因此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()
# do what you will with root or sub-trees of it.

为了改变这种关系,我们有类似下面的内容。这里,Facade类需要Subsystem实例才能工作,因此他们使用强-ref来获取所需的子系统。但是,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

3 个答案:

答案 0 :(得分:29)

是的,weakref在这里非常出色。具体而言,而不是:

self.children = {}

使用:

self.children = weakref.WeakValueDictionary()

您的代码中没有其他任何需要更改的内容。这样,当一个孩子没有其他差异时,它就会消失 - 父母的children地图中以该孩子为值的条目也会消失。

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

答案 1 :(得分:18)

我建议使用child.parent = weakref.proxy(self)。如果parent的(外部引用)的生命周期涵盖child的生命周期,这是避免循环引用的好方法。相反,当weakref的生命周期涵盖child的生命周期时,请child使用parent(正如Alex建议的那样)。但是,如果weakrefparent在没有其他情况下可以活着,就永远不要使用child

这些规则用例子说明。如果将root存储在某个变量中并使用它,则使用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

如果将所有子项绑定到变量,则使用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

答案 2 :(得分:1)

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

逻辑步骤1.

您需要确保有强引用,只要您需要它们,就可以保持所有对象的存活。它可以通过多种方式完成,例如:

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

逻辑步骤2.

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

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

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

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

逻辑步骤3.

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

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


步骤1中的所有引用必须是强大的

步骤2和3中的所有引用可能都很弱或很强。使用强引用没有任何好处。在您知道循环不再可能之前,使用弱引用是有利的。严格地说,一旦你知道循环是不可能的,那么使用弱引用还是强引用没有区别。但是为了避免考虑它,你可以在步骤2和3中仅使用弱引用。