【问题标题】:Python creates a redundant reference to a classPython 创建一个对类的冗余引用
【发布时间】:2018-06-14 15:39:15
【问题描述】:

我观察到 Python 2.7.12 和 Python 3.5.2 的一个奇怪行为:

import sys

class Foo:
    def __init__(self):
        self.b = self.bar
    def bar(self):
        pass

f = Foo()
print(sys.getrefcount(f) - 1) # subtract the extra reference created by
                              # passing a reference to sys.getrefcount.

当我在 python 中运行代码时,我得到 2,这意味着有两个对 Foo 对象的引用。唯一的解决方案是删除self.b = self.bar。它似乎创建了一个循环引用。

谁能解释一下这种行为?

更新: 以下是运行结果:

class Foo:
    def __init__(self):
        self.b = self.bar

    def bar(self):
        pass

    def __del__(self):
        print "deleted"

def test_function():
    f = Foo()
    print f

if __name__ == "__main__":
    for i in xrange(5):
        test_function()
    print "finished"

python ./test.py
<__main__.Foo instance at 0x104797e60>
<__main__.Foo instance at 0x104797ef0>
<__main__.Foo instance at 0x104797f38>
<__main__.Foo instance at 0x104797f80>
<__main__.Foo instance at 0x104797fc8>
finished

正如您在每次迭代中看到的那样,python 会创建 Foo 类的新实例,但永远不会释放它们!

更新 2 和根本原因

具有 del() 方法且属于引用循环的对象 导致整个参考周期无法收集,包括 对象不一定在循环中,但只能从循环中到达。 https://docs.python.org/2/library/gc.html#gc.garbage

Foo 类实例同时具有__del__ 方法和绑定方法self.b = self.bar,这意味着它绝对是引用循环的一部分。 因此,根据 py 文档,该实例是 uncollectable

【问题讨论】:

  • 你是说 Foo 永远不会被收集吗?你能提供代码来证明是这种情况吗?仅仅打印引用计数并不能令人信服,因为引用计数非零的对象仍然可以被收集。
  • @Kevin,请在更新中找到结果。
  • @fsqirrel 很好 __del__ 函数不会删除,它必须调用 super().__del__ 或者你只是做 del 约,不是 100% 肯定但值得一试
  • 感谢您发布您的结果。当我在 2.7 中运行您的代码时,我得到与您相同的输出,但是当我将其移植到 3.X(将打印语句更改为函数,并将 xrange 更改为范围)时,“完成”后出现“已删除”消息。不过,即使在 3.X 中,我也不会依赖这种情况,因为文档说“不能保证 __del__() 方法会在解释器退出时为仍然存在的对象调用。”我本来建议在程序结束之前手动调用gc.collect(),但奇怪的是,这似乎并没有影响 2.7 中的结果。

标签: python python-2.7 garbage-collection


【解决方案1】:

额外的引用隐藏在f.b中存储的绑定方法中。

print(f.b.__self__) # <__main__.Foo object at 0x000001CD147FEF98>

在存储self.b = self.bar 时,您创建了一个绑定方法。绑定方法跟踪它所绑定的实例。这就是允许它隐式传递self 的原因。

如果您要手动创建绑定方法,也会发生同样的情况。

import sys

class Foo:
    def bar(self):
        pass

f = Foo()
print(sys.getrefcount(f) - 1) # 1

bound_method = f.bar

print(sys.getrefcount(f) - 1) # 2

所以你是对的:实例化绑定方法会创建引用循环f -&gt; f.b -&gt; f。不过,这不是问题,因为 Python 从 version 2.0 开始处理循环引用。

【讨论】:

  • 感谢您的及时回复和详细解释!
  • @fsquirrel 您似乎在另一条评论中提到您的 Foo 实例没有被收集?这有助于解决问题吗?如果它没有使用 c 扩展,或者实现一些自定义的 __del__ 方法?
  • 据我所知,我们没有扩展。但是该类确实有一个自定义的__del__ 方法。它调用os.fdclose。它会阻止垃圾收集吗?是的,您的解决方案解决了这个问题:)
  • 请在更新中找到测试运行的结果。
猜你喜欢
  • 2016-06-29
  • 2018-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多