我有一些代码,其中类的实例具有父< - >子引用,例如:
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()完成后不会释放root
,c1
和c2
,对吧?那么,如何让它们被释放?我想我可以做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
答案 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建议的那样)。但是,如果weakref
和parent
在没有其他情况下可以活着,就永远不要使用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.
您需要确保有强引用,只要您需要它们,就可以保持所有对象的存活。它可以通过多种方式完成,例如:
逻辑步骤2.
现在,如果需要,您可以添加表示信息的引用。
例如,如果在步骤1中使用[容器]方法,则仍需表示边缘。节点A和B之间的边缘可以用单个参考表示;它可以向任何一个方向发展。同样,有很多选项,例如:
当然,如果您在步骤1中使用了[root + children]方法,那么您的所有信息都已完全显示,因此您跳过此步骤。
逻辑步骤3.
现在,如果需要,您可以添加引用以提高性能。
例如,如果您在步骤1中使用[container]方法,并且在步骤2中使用[children]方法,则可能希望提高某些算法的速度,并在每个节点及其父节点之间添加引用。这些信息在逻辑上是多余的,因为您可以(以性能为代价)从现有数据中获取信息。
步骤1中的所有引用必须是强大的。
步骤2和3中的所有引用可能都很弱或很强。使用强引用没有任何好处。在您知道循环不再可能之前,使用弱引用是有利的。严格地说,一旦你知道循环是不可能的,那么使用弱引用还是强引用没有区别。但是为了避免考虑它,你可以在步骤2和3中仅使用弱引用。