什么是 __del__ 方法,我该如何调用它?

174

我看到一个类中定义了一个__del__方法。这个方法用于销毁该类的实例。然而,我找不到使用这个方法的地方。这个方法应该如何使用?像这样:obj1.del()吗?

如何调用__del__方法?


对于读者们:请查看mCoding的这个视频,其中解释了__del__的工作原理,并提供了一些替代方案。 - InSync
@InSync "给读者们:看看这个mCoding的视频吧,..." -- 好吧,更适合那些不能阅读的人们;-)(重点是我的) - András Aszódi
@AndrásAszódi 无法阅读的人可能会使用屏幕阅读器。 - InSync
@AndrásAszódi 无法阅读的人可能会使用屏幕阅读器。 - undefined
@InSync 我指的是不识字的人(无论是功能性还是其他原因),而不是视觉障碍者。但是没错,你说得对,有几种技术可以帮助同时患有这两种情况的人。 - András Aszódi
@InSync 我指的是不识字的人(无论是功能性的还是其他原因),而不是视觉障碍的人。但是没错,你是对的,有几种技术可以帮助同时患有这两种情况的人。 - undefined
5个回答

243

__del__是一个终结器(finalizer)。当一个对象的所有引用被删除后,该对象会被垃圾回收,此时__del__将被调用。

简单情况下,这可能会在你执行del x之后立即发生,或者如果x是一个局部变量,则在函数结束后发生。特别地,如果没有循环引用,CPython(标准Python实现)将立即进行垃圾回收。*

然而,这是CPython的一个实现细节。Python垃圾回收的唯一必须的属性是它发生在所有引用被删除之后,因此这可能不会立即发生,也可能根本不会发生

此外,变量可以因为多种原因而存活很长时间,例如传播的异常或模块内部检查可以使变量引用计数大于0。另外,变量可以是引用循环的一部分——开启垃圾回收的CPython会打破大多数但不是全部这样的循环,而且只会定期进行。

由于无法保证__del__()被执行,因此永远不应该将需要运行的代码放入其中——相反,这些代码应该属于try语句的finally子句或with语句的上下文管理器。但是,__del__也有合法的使用情况:例如,如果一个对象X引用了Y并且还在全局cache中保留了Y的引用副本(cache['X -> Y'] = Y),那么X.__del__也应该删除缓存条目。

如果你知道析构函数提供了(违反上述准则的)必需清理,你可能想要直接调用它,因为它作为一个方法没有任何特殊之处:x.__del__()。显然,只有在你知道它可能会被调用两次时才应该这样做。或者,作为最后的手段,你可以使用重新定义该方法:

type(x).__del__ = my_safe_cleanup_method

* 参考资料:

CPython 实现细节: CPython 目前使用引用计数方案,通过(可选的)延迟检测循环引用垃圾来收集大多数成为不可达对象的对象[...] 其他实现方式可能有所不同,而且 CPython 也可能会改变。


在这个上下文中,__exit__是什么意思?它是在__del__之前还是之后运行,还是一起运行? - lony
你也可以使用一个类变量来判断是否已经调用了 __del__,这样就不必担心第二次调用的问题。 - SarcasticSully
5
“might not happen at all” 是否包括程序终止时? - Andy Hayden
5
@AndyHayden:即使在程序终止时,__del__方法可能也不会运行。即使它们在终止时运行,编写一个能够正常工作的__del__方法,即使解释器正在自毁,也需要比许多程序员应用更谨慎的编码。 (CPython清理通常会在解释器关闭时运行__del__方法,但仍有一些情况不足够。守护线程、C级全局变量和在另一个__del__中创建的具有__del__的对象都可能导致__del__方法无法运行。) - user2357112
4
@bombs,它不是析构函数的原因与__init__不是构造函数的原因相同。两者都不会实际创建或销毁对象。__init__方法通过调用它时已经存在的对象来初始化该对象(这也是为什么它不返回任何内容的原因)。同样,当__del__退出时,对象仍然存在:它只是在实际销毁之前执行任何清理(“最终化”)可能需要完成的工作。 - Peter Hansen
显示剩余2条评论

118

我原本为另一个问题写了答案,不过这个问题更加准确。

构造函数和析构函数是如何工作的?

以下是有点主观的答案。

不要使用__del__。这不是C++或者专门为析构函数设计的语言。在Python 3.x中,__del__方法应该被废弃,尽管我确定会有人找到有意义的用例。如果你必须使用__del__,请注意一下基本限制(详见http://docs.python.org/reference/datamodel.html):

  • __del__方法只有在垃圾收集器正在回收对象时才会被调用,并不是在你失去了对一个对象的最后引用或者执行del object时调用的。
  • __del__方法负责调用超类中任何__del__方法,尽管不清楚这是按照方法解析顺序还是直接调用每个超类。
  • __del__方法意味着垃圾收集器放弃检测和清理任何循环链接,例如失去对链表的最后引用。可以从gc.garbage中获取被忽略的对象列表。有时可以使用弱引用来完全避免这种情况。这个问题经常被讨论:请参见http://mail.python.org/pipermail/python-ideas/2009-October/006194.html
  • __del__函数可以作弊,保存一个对象的引用,并停止垃圾收集。
  • __del__中显式抛出的异常会被忽略。
  • __del____init__ 更多地补充了__new__。这容易引起混淆。有关说明和注意事项,请参见http://www.algorithm.co.il/blogs/programming/python-gotchas-1-del-is-not-the-opposite-of-init/
  • __del__在Python中不受欢迎。您会发现sys.exit()文档未指定退出之前是否收集垃圾,并且存在许多奇怪的问题。调用全局变量上的__del__会导致奇怪的排序问题,例如http://bugs.python.org/issue5099。如果__init__失败,是否应该调用__del__?请参见http://mail.python.org/pipermail/python-dev/2000-March/thread.html#2423进行长时间讨论。
  • 但是,另一方面:

    而我个人不喜欢使用__del__的原因是:

    • 每次有人提到__del__,它都会退化为三十条令人困惑的消息。
  • 它打破了Python之禅中的以下几项原则:
    • 简单胜于复杂。
    • 特殊情况并不足以打破规则。
    • 错误不应该悄无声息地传递。
    • 在存在歧义的情况下,拒绝猜测的诱惑。
    • 应该有一种明显的方式来做到这一点,而最好只有一种方式。
    • 如果实现难以解释,那就是个坏主意。

    因此,请找到一个不使用__del__的理由。


    7
    即使问题不是为什么我们不应该使用 __del__,而是如何调用 __del__,您的答案也很有趣。 - nbro
    1
    另外,我忘了提到PyPy(一种更快的解释器,适用于长时间运行的应用程序)将在__del__中断。 - Charles Merriam
    @CharlesMerriam 感谢您的回答! - Tom Burrows
    4
    你提到过 "del 是在垃圾回收器正在回收对象时调用的,而不是在你失去对对象的最后一个引用时调用的"。然而,文档清楚地说明了:“注意:del x 不会直接调用 x.del() - 前者将 x 的引用计数减少一,后者只有在 x 的引用计数达到零时才会调用”。因此,当你失去对对象的最后一个引用时,del 总是会被调用。 - Tushar Vazirani
    1
    这要比我担心的更加随机。有许多原因不会丢失最后的引用,其中许多是短暂的,这使得推理和调试变得困难。而且实现上存在差异,并且存在硬性停止,以及……尝试识别贪婪的巴格布拉兽并使用毛巾避免它。 - Charles Merriam
    显示剩余2条评论

    20

    __del__方法会在对象被垃圾回收时调用。但请注意,它并不能保证一定会被调用。以下代码本身不一定会触发它:

    del obj
    

    这是因为del仅将对象的引用计数减少了一个。如果有其他东西对该对象有引用,则不会调用__del__

    使用__del__有一些注意事项,通常它们并不是非常有用。听起来更像是您想要使用一个close方法或者可能是with语句

    请参阅Python文档中关于__del__方法的说明

    还有一件事需要注意: 如果滥用__del__方法,将会阻止垃圾回收。特别是,具有多个带有__del__方法的对象的循环引用将不会被垃圾回收。这是因为垃圾回收器不知道先调用哪个方法。请参阅gc模块的文档获取更多信息。


    从Python 3.4开始,已经实现了PEP 442,使得带有__del__方法的对象可以被垃圾回收。 - Noctis Skytower

    14

    __del__方法(注意拼写!)在对象最终被销毁时调用。从技术上讲,在cPython中,当您的对象没有更多的引用,即超出范围时,该方法将被调用。

    如果您想删除对象并因此调用__del__方法,请使用

    del obj1
    

    这会删除该对象(前提是没有其他引用它)。

    我建议您编写一个类似于以下代码的小类:

    class T:
        def __del__(self):
            print "deleted"
    

    请在Python解释器中进行调查,例如:

    >>> a = T()
    >>> del a
    deleted
    >>> a = T()
    >>> b = a
    >>> del b
    >>> del a
    deleted
    >>> def fn():
    ...     a = T()
    ...     print "exiting fn"
    ...
    >>> fn()
    exiting fn
    deleted
    >>>   
    

    请注意,jython和ironpython在对象被删除和调用__del__时有不同的规则。然而,由于此原因以及在调用时对象及其环境可能处于未知状态,使用__del__并不被认为是良好的实践。同时也不能保证一定会调用__del__ - 解释器可能以多种方式退出而不删除所有对象。请保留HTML标签。


    3
    如前所述,__del__ 功能有些不可靠。如果在某些情况下似乎有用,请考虑改用 __enter____exit__ 方法。这将提供类似于用于访问文件的 with open() as f: pass 语法的行为。当进入 with 的作用域时,会自动调用 __enter__,而当退出时,则会自动调用 __exit__。有关更多详细信息,请参见此问题

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