【问题标题】:setattr, object deletion and cyclic garbage collectionsetattr、对象删除和循环垃圾回收
【发布时间】:2012-03-22 17:27:25
【问题描述】:

我想了解对象删除在 python 上的工作原理。这是一组非常简单的代码。

class A(object):

    def __init__(self):
        setattr(self, "test", self._test)

    def _test(self):
        print "Hello, World!"

    def __del__(self):
        print "I'm dying!"

class B(object):

    def test(self):
        print "Hello, World!"

    def __del__(self):
        print "I'm dying"

print "----------Test on A"
A().test()
print "----------Test on B"
B().test()

Pythonista 会识别出我运行的是 python 2.x 版本。更具体地说,此代码在 python 2.7.1 设置上运行。

此代码输出以下内容:

----------Test on A
Hello, World!
----------Test on B
Hello, World!
I'm dying

令人惊讶的是,A 对象没有被删除。我可以理解为什么,因为__init__ 中的setattr 语句会产生循环引用。不过这个好像很容易解决。

最后,这个页面,在python documentation (Supporting Cyclic Garbage Collection),表明处理这种循环引用是可能的。

我想知道:

  • 为什么我从不通过 A 类中的 __del__ 方法?
  • 如果我对循环引用的诊断是好的,为什么我的object 子类不支持循环垃圾回收?
  • 最后,如果我真的想通过__del__,如何处理这种setattr

注意:A 中如果setattr 指向我模块的另一个方法,则没有问题。

【问题讨论】:

  • 我怀疑setattr 与它有什么关系。您正在存储一个绑定方法,该方法存储对象。您不妨通过常规分配 (self.test = self._test) 存储它。请尝试一下,如果它产生相同的输出,您可以简化问题。
  • 哦,是的,它产生了同样的结果。但是,self.test = self._test 调用对象的 __setattr__ 属性。我说的对吗?
  • 可以,但setattr 也应该这样做。我相信setattr 甚至尊重属性。

标签: python garbage-collection setattr


【解决方案1】:

事实 1

实例方法通常存储在中。解释器首先在实例__dict__ 中查找它们,但失败了,然后再查找成功的类。

当您在__init__ 中动态设置A 的实例方法时,您会在实例字典中创建对它的引用。这个引用是循环的,所以引用计数永远不会归零,引用计数器也不会清除A

>>> class A(object):
...     def _test(self): pass
...     def __init__(self):
...             self.test = self._test
... 
>>> a = A()
>>> a.__dict__['test'].im_self

事实 2

垃圾收集器是 Python 用来处理循环引用的工具。不幸的是,它无法使用__del__ 方法处理对象,因为通常它无法确定调用它们的安全顺序。相反,它只是将所有此类对象放在gc.garbage 中。然后你可以去那里打破循环,这样它们就可以被释放。来自docs

gc.garbage

收集器发现无法访问但可以访问的对象列表 不被释放(无法收集的对象)。默认情况下,此列表仅包含 具有__del__() 方法的对象。具有__del__() 方法的对象 并且是参考周期的一部分导致整个参考周期 不可收藏,包括不一定的物品 在循环中,但只能从它到达。 Python 不收集这样的 自动循环,因为一般来说,Python 不可能 猜测运行__del__() 方法的安全顺序。如果你 知道一个安全的顺序,你可以通过检查垃圾来强制问题 列表,并明确打破循环,因为你的对象在 列表。请注意,即使如此,这些对象也可以通过以下方式保持活动状态 在垃圾列表中,所以它们也应该从垃圾中删除。 例如,在中断循环后,执行del gc.garbage[:] 清空 列表。通常最好通过不创建循环来避免该问题 包含具有__del__() 方法的对象,并且可以检查garbage 在这种情况下,以验证没有创建此类循环。

因此

如果您希望对象被垃圾回收,请不要使用 __del__ 方法对对象进行循环引用。

【讨论】:

  • 非常很烦人。事实上,我总是知道如何打破循环。但是我想在删除对象时这样做,这就是在很多情况下我们“覆盖”__del__ 语句的原因(否则,我为什么要写这个语句;))。事实上,我可以通过调用del gc.garbage[:] 查看这个gc.garbage 列表、中断循环和删除对象,但是,正如我所说,我发现这......很烦人。感谢您的明智和快速响应。
  • @ohe 我显然不知道你的具体情况,但我认为更自然的解决方案是定义某种.finish() 方法——you 完成后打电话——这会打破循环。
【解决方案2】:

您应该仔细阅读documentation on the __del__ 方法 - 特别是带有__del__ 方法的对象会改变收集器工作方式的部分。

gc module 提供了一些挂钩,您可以在其中自行清理。

我怀疑这里没有__del__ 方法会导致您的对象被正确清理。您可以通过查看gc.garbage 并查看您的A 实例是否存在来验证这一点。

【讨论】:

  • 第一行文档:当实例即将被销毁时调用....在启用选项周期检测器时检测到垃圾的圆形引用(默认情况下),但只能如果不涉及 Python 级别的 __del__() 方法,则进行清理。那么在python级别放置一个 del 方法会破坏循环检测器吗?我们该如何处理呢?
  • ohe:我的回答将您指向 gc 模块 garbage 变量,您可以对其进行操作以打破循环。
猜你喜欢
  • 1970-01-01
  • 2013-05-11
  • 1970-01-01
  • 1970-01-01
  • 2017-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-30
相关资源
最近更新 更多