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

176 投票
5 回答
203531 浏览
提问于 2025-04-15 14:37

我看到一个类里面定义了一个 __del__ 方法。这个方法是用来销毁这个类的实例的。不过,我找不到这个方法被使用的地方。这个方法是怎么用的呢?像这样: obj1.del() 吗?

我该怎么调用 __del__ 方法呢?

5 个回答

20

__del__ 方法是在对象被垃圾回收时会被调用的。不过,不能保证它一定会被调用。下面这段代码本身并不一定会触发它:

del obj

原因是,del 只是把这个对象的引用计数减一。如果还有其他地方引用着这个对象,__del__ 就不会被调用。

使用 __del__ 方法时有一些注意事项。一般来说,它们的用处并不大。听起来你可能更想用一个关闭方法,或者使用 with 语句

可以查看 关于 __del__ 方法的 Python 文档

还有一点需要注意:如果过度使用 __del__ 方法,可能会影响垃圾回收。特别是,如果有多个对象之间存在循环引用,并且它们都有 __del__ 方法,那么这些对象就不会被垃圾回收。这是因为垃圾回收器不知道应该先调用哪个方法。想了解更多,可以查看 gc 模块的文档

119

我之前写了一个回答,虽然这个问题更准确。

构造函数和析构函数是怎么工作的?

这里有一个稍微带有个人观点的回答。

不要使用 __del__。这不是 C++,也不是为了析构函数而设计的语言。其实在 Python 3.x 中,__del__ 方法应该被淘汰,尽管我相信总会有人找到合适的使用场景。如果你必须使用 __del__,要注意以下基本限制,详细信息可以参考 http://docs.python.org/reference/datamodel.html

  • __del__ 是在垃圾回收器收集对象时被调用的,而不是在你失去对某个对象的最后引用时,也不是在你执行 del object 时。
  • __del__ 需要调用父类中的任何 __del__,但这是否按照方法解析顺序(MRO)来调用并不明确。
  • 如果有 __del__,垃圾回收器就会放弃检测和清理任何循环引用,比如失去对链表的最后引用。你可以通过 gc.garbage 获取被忽略的对象列表。有时可以使用弱引用来完全避免循环引用。这个问题时不时会被讨论:见 http://mail.python.org/pipermail/python-ideas/2009-October/006194.html
  • __del__ 函数可以“作弊”,保存对某个对象的引用,从而阻止垃圾回收。
  • __del__ 中显式引发的异常会被忽略。
  • __del__ 更像是 __new__ 的补充,而不是 __init__。这会让人感到困惑。可以查看 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__

245

__del__ 是一个终结器。它会在一个对象被垃圾回收时被调用,也就是说,当所有指向这个对象的引用都被删除后,某个时刻就会发生垃圾回收。

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

不过,这只是 CPython 的一个实现细节。Python 垃圾回收的唯一必要特性是,它发生在所有引用被删除之后,所以这并不一定会在马上发生,也可能根本不会发生

更重要的是,变量可能因为多种原因而存活很长时间,比如一个传播的异常或者模块的自省可能会让变量的引用计数大于 0。此外,变量也可能是引用循环的一部分——开启垃圾回收的 CPython 会打破大部分这样的循环,但并不是所有的,而且即使是这样,也只是定期进行。

因为你不能保证 __del__() 会被执行,所以绝对不要把需要运行的代码放在 __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 也可能会改变。

撰写回答