如何以及何时在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循环引用,这样root
, c1
和c2
在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中只使用弱引用。