GC不会删除WeakKeyDictionaries中的循环引用?

时间:2011-06-02 03:21:58

标签: python garbage-collection weak-references

我有一种情况,只要第一个对象存在,我就想保持从一个对象到另一个对象的映射。我的第一个想法是使用WeakKeyDictionary。

import weakref
import gc

class M:
    pass

w = weakref.WeakKeyDictionary()
m = M()
w[m] = some_other_object
del m
gc.collect()
print w.keys()

在大多数情况下这很好用。但是,在some_other_object是(或引用)m的情况下,M实例不会被垃圾回收。 (要查看示例,请将some_other_object替换为m

当然,如果我将映射存储在对象本身上,当我删除它时会被垃圾收集:

import weakref
import gc

class M:
    pass

m = M()
m.circular_reference = m
r = weakref.ref(m)
del m
gc.collect()
print r

我可以使用weakref模块实现第二个例子的结果(即没有变异m)吗?

换句话说,我可以使用weakref模块将对象映射到自身(或其他具有强引用的对象),并且只要有对象的其他引用,只将对象保留在内存中吗?

2 个答案:

答案 0 :(得分:2)

在这些示例中,您实际上没有循环引用。你的圈子就像:

WeakKeyDict - >价值 - >键 - > ...

因此,dict使值保持活动状态,从而使密钥保持活动状态,并通过不涉及强引用的单独机制,密钥指示dict保持值保持活动状态。由于此处没有正确的参考圆,因此GC从不会检测到任何问题。 (但是,你的第二个例子 包含一个循环引用,这就是为什么它的行为与第一个一样)

我怀疑你的问题的唯一解决方案是确保dict的值永远不会对dict中的任何键具有强引用(直接或间接)。这与srgerg提出的类似,但是你真的希望对键的引用变弱,而不是对所有值进行弱引用。例如:

import weakref
import gc

class M:
    pass

w = weakref.WeakKeyDictionary()
key = M()
val = M()
val.keyRef = weakref.ref(val)
w[key] = val
print len(w)   ## prints 1
del key
gc.collect()
print len(w)   ## prints 0

这样,总是会对值进行强引用,但是您正在仔细控制对键的引用,以确定从dict中删除的内容。根据程序的复杂程度,实现这可能非常耗时,因为您需要手动跟踪对键的所有引用。

但是如果你告诉我们更多关于具体问题的话,也许我们可以提出一个更简单的解决方案。

答案 1 :(得分:1)

处理这样的循环引用很可能会让人感到高兴,但是,无论如何,我会尽力回答你的问题。

python字典存储对其键及其值的引用。 WeakKeyDictionary存储对其键的引用,但引用其值。因此,在您的第一个示例中,如果您设置w[m] = m,则字典wm的弱引用为关键字,并强烈引用m作为值。

Python的weakref模块还有一个WeakValueDictionary,它对其键和对其值的弱引用有强引用,但这不能解决您的问题。

你真正想要的是一个对其键和值都有弱引用的字典。以下代码显式地使字典中的每个值成为弱引用:

import weakref
import gc

class M():
  pass

class Other():
  pass

m = M()
w = weakref.WeakKeyDictionary()
w[m] = weakref.ref(m)
print len(w.keys()) # prints 1
del m
print len(w.keys()) # prints 0

m = M()
o = Other()
o.strong_ref = m
w[m] = weakref.ref(o)
print len(w.keys()) # prints 1
del m
print len(w.keys()) # prints 1
del o
print len(w.keys()) # prints 0

您可以通过对WeakKeyDictionary进行子类化来自动使值成为弱引用。但请注意,的行为与真正的WeakValueDictionary相似,因为它没有回调来在删除值时通知字典。

class MyWeakKeyValueDictionary(weakref.WeakKeyDictionary):
    def __init__(self):
        weakref.WeakKeyDictionary.__init__(self)

    def __setitem__(self, key, value):
        weakref.WeakKeyDictionary.__setitem__(self, key, weakref.ref(value))

w2 = MyWeakKeyValueDictionary()
m = M()
w2[m] = m
print len(w2.keys()) # prints 1
del m
print len(w2.keys()) # prints 0

m = M()
o = Other()
o.strong_ref = m
w2[m] = o
print len(w2.keys()) # prints 1
del m
print len(w2.keys()) # prints 1
del o
print len(w2.keys()) # prints 0

但是,最后,我担心尝试处理这样的循环引用可能会导致更多问题而不是它的价值。