Python类和类属性何时被垃圾回收?

6
class Member(object):
    def __init__(self, identifier):
        self.identifier = identifier
        print "Member __init__", self.identifier

    def __del__(self):
        print "Member __del__", self.identifier
        with open("/home/might/" + self.identifier, "w") as outF:
            outF.write(self.identifier)

class WithMembers(object):
    def __init__(self):
        print "WithMembers __init__"
        print WithMembers.classMem
        self.instanceMem = Member("instance mem")

    def __del__(self):
        print "WithMembers __del__"

    classMem = Member("class mem")

if __name__ == "__main__":
    print "main"
    WithMembers()
    #del WithMembers.classMem       # "Member __del__ class mem" before "end"
    #del WithMembers                # "Member __del__ class mem" after "end"
    print "end"

上述代码在 Hidden.py 文件中,运行 python Hidden.py 将产生以下输出结果:
Member __init__ class mem
main
WithMembers __init__
<__main__.Member object at 0x935aeec>
Member __init__ instance mem
WithMembers __del__
Member __del__ instance mem
end

除非我取消其中一个del语句,否则我不会在输出或class mem文件中看到Member __del__ class mem。为什么会这样?Python类和类属性何时进行垃圾回收?


1
你的代码与垃圾回收无关。del <identifier>会取消引用该标识符,因此它不再指向现有对象。当发生这种情况时,将调用your_object.__del__。稍后,在没有对该对象的任何引用后,它将被垃圾回收。 - Adam Smith
可能永远不会发生,这一点已经有很好的记录了。考虑使用上下文管理器。 - cdarke
@cdarke:确实很好地记录了,并且可能永远不会依赖于Python版本。我进行了一些研究并发布了https://dev59.com/E4fca4cB1Zd3GeqPouG7#28552889。 - Might
2个回答

5

这个问题在http://bugs.python.org/issue1545463中被报告为一个错误,在3.4中得到了修复,但没有向后兼容(我正在运行2.7)。这也在http://code.activestate.com/lists/python-list/504216/中解释过。请参见下面的Python 3.5输出。

基于上述内容,我的理解是,在2.7中,新样式类WithMembers在解释器退出时仍然存在(未被GC清除)。因此,classMem没有被垃圾回收,因为WithMembers仍然引用它。

请注意,新样式类从__mro__和一些内置描述符(http://bugs.python.org/issue17950)中对自身有循环引用。即使模块级别的新样式类在模块清理后被认为已死亡,但在模块清理后调用GC清理它们的调用被禁用,因为它会导致太多其他问题。

这不会导致内存泄漏,因为操作系统在解释器退出后会清理资源。

class Member(object):
    def __init__(self, identifier):
        self.identifier = identifier
        print("Member __init__ " + self.identifier)

    def __del__(self):
        print("Member __del__ " + self.identifier)
        with open("/home/might/" + self.identifier, "w") as outF:
            outF.write(self.identifier)

class WithMembers(object):
    def __init__(self):
        print("WithMembers __init__")
        print(WithMembers.classMem)
        self.instanceMem = Member("instance mem")

    def __del__(self):
        print("WithMembers __del__")

    classMem = Member("class mem")

if __name__ == "__main__":
    print("main")
    WithMembers()
    print("end")

运行 python3 Hidden.py 时,输出如下:

Member __init__ class mem
main
WithMembers __init__
<__main__.Member object at 0xb6fc8e2c>
Member __init__ instance mem
WithMembers __del__
Member __del__ instance mem
end
Member __del__ class mem
Exception ignored in: <bound method Member.__del__ of <__main__.Member object at 0xb6fc8e2c>>
Traceback (most recent call last):
  File "class_member_gc.py", line 8, in __del__
NameError: name 'open' is not defined

0

classMem是类WithMembers的类变量,这意味着它将被该类的所有实例共享。在Python中,这是一种全局变量。 这就是为什么当程序退出时,类成员Member__del__没有被调用的原因。

接下来有一个问题:为什么Python不直接在退出程序时将所有引用计数设置为0,以便所有__del__函数都可以被调用呢?

就像C++一样,它保证了全局变量的析构函数会被调用。在Python中唯一保证这一点的方法是遍历所有模块并删除它们的所有变量。但这意味着__del__方法不能相信任何它可能想要使用的全局变量仍然存在,因为无法知道变量删除的顺序。

强制销毁classMem的两种方法。

第一种是del WithMembers.classMem。这将把引用计数减少到0,并自动调用__del__

另一种方法是使类WithMembers成为旧式类(不继承自object)。像这样:

...

class WithMembers:
    def __init__(self):
        print "WithMembers __init__"
        print WithMembers.classMem
        self.instanceMem = Member("instance mem")
...        

输出将会是:

Member __init__ class mem
main
WithMembers __init__
<__main__.Member object at 0x00000000026C5278>
Member __init__ instance mem
WithMembers __del__
Member __del__ instance mem
end
Member __del__ class mem

这里有一个非常有用的链接,可以帮助您更好地理解这个答案。 http://www.electricmonk.nl/log/2008/07/07/python-destructor-and-garbage-collection-notes/

希望能对您有所帮助。:)


感谢您指出旧式类,这使我找到了https://dev59.com/E4fca4cB1Zd3GeqPouG7#28552889中引用的链接。我不认为我完全同意您引用文章中的所有内容,特别是“程序退出”部分:http://legacy.python.org/search/hypermail/python-1993/0110.html是GvR在`__del__`存在之前考虑的,而不是当前实现背后的原因。 - Might

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