垃圾回收后如何保留循环引用

3
import weakref
import gc

class MyClass(object):
    def refer_to(self, thing):
        self.refers_to = thing

foo = MyClass()
bar = MyClass()
foo.refer_to(bar)
bar.refer_to(foo)
foo_ref = weakref.ref(foo)
bar_ref = weakref.ref(bar)
del foo
del bar
gc.collect()
print foo_ref()

我希望您将foo_refbar_ref分别与foobar保持弱引用,只要它们相互引用 *,但实际上打印的是None. 我该如何防止垃圾回收器收集引用循环中的某些对象?
在这段代码中,bar应该被垃圾回收,因为它不再是foo-bar引用循环的一部分。
baz = MyClass()
baz.refer_to(foo)
foo.refer_to(baz)
gc.collect()

* 我知道防止循环引用被垃圾回收可能看起来毫无意义,但我的使用场景需要这样做。我有一堆对象以网状方式相互引用,并且还有一个WeakValueDictionary,它在这一堆对象中保留对每个对象的弱引用。我只希望当一个对象孤立无援时,也就是没有其他对象在这一堆中引用它时,才将其进行垃圾回收。

1个回答

2

通常情况下,使用弱引用意味着您无法防止对象被垃圾回收。

然而,有一个技巧可以用来防止参与引用循环的对象被垃圾回收:在这些对象上定义一个__del__()方法

gc模块文档中可以看到:

gc.garbage

垃圾回收器发现不可达但无法释放(不可回收)的对象列表。默认情况下,此列表仅包含具有__del__()方法的对象。具有__del__()方法并且是引用循环的一部分的对象会导致整个引用循环无法回收,包括不一定在循环中但仅从循环中可达的对象。Python不会自动回收这样的循环,因为通常情况下,Python无法猜测运行__del__()方法的安全顺序。如果您知道一个安全的顺序,可以通过检查垃圾列表并显式地打破列表中您对象引起的循环来强制执行。请注意,这些对象仍然通过位于垃圾列表中而保持活动状态,因此也应从垃圾中删除。例如,在打破循环后,执行del gc.garbage [:]以清空列表。通常最好避免创建包含具有__del__()方法的对象的循环,并且在那种情况下可以检查垃圾以验证没有创建这样的循环。

当您按以下方式定义MyClass时:

class MyClass(object):
    def refer_to(self, thing):
        self.refers_to = thing
    def __del__(self):
        print 'Being deleted now, bye-bye!'

然后你的示例脚本会打印出:
<__main__.MyClass object at 0x108476a50>

但是注释掉其中一个.refer_to()调用会导致:

Being deleted now, bye-bye!
Being deleted now, bye-bye!
None

换句话说,通过定义__del__()方法,我们防止了引用循环被垃圾回收,但任何孤立的对象都将被删除。
需要注意的是,为了使其正常工作,您需要有循环引用;如果您对象图中的任何对象不是引用循环的一部分,则会被单独处理。

1
当然,这是一个巨大的hack和极度特定于实现的。其他实现可能没有那个“功能”,未来的CPython版本可能会取消它。我认真考虑重新设计代码,不需要这个功能。 - user395760
@delnan:不,这实际上与实现无关;垃圾回收无法确定删除的顺序,因此根本不执行。这同样适用于Jython和PyPy。 - Martijn Pieters
1
垃圾回收器无法确定哪种顺序最合理,但它可以选择任意顺序。实际上,PyPy的GC就是这样做的。而且,Java语言规范明确声明了终结顺序未指定,因此我想JVM也有同样的自由(我不会打赌我的代码正确性取决于Jython采取措施来反对这一点)。 - user395760
@delnan:没错,很有趣;因此,尽管这可能会破坏一些东西,PyPy还是明确选择调用__del__,而CPython则明确选择不这样做。我不知道PyPy已经做出了这个选择。 :-) - Martijn Pieters
+1,虽然需要这种设计的方案值得怀疑,但这是一个聪明的解决方案。 - John La Rooy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接