什么是__del__方法,如何调用它?
我看到一个类里面定义了一个 __del__
方法。这个方法是用来销毁这个类的实例的。不过,我找不到这个方法被使用的地方。这个方法是怎么用的呢?像这样: obj1.del()
吗?
我该怎么调用 __del__
方法呢?
5 个回答
__del__
方法是在对象被垃圾回收时会被调用的。不过,不能保证它一定会被调用。下面这段代码本身并不一定会触发它:
del obj
原因是,del
只是把这个对象的引用计数减一。如果还有其他地方引用着这个对象,__del__
就不会被调用。
使用 __del__
方法时有一些注意事项。一般来说,它们的用处并不大。听起来你可能更想用一个关闭方法,或者使用 with 语句。
可以查看 关于 __del__
方法的 Python 文档。
还有一点需要注意:如果过度使用 __del__
方法,可能会影响垃圾回收。特别是,如果有多个对象之间存在循环引用,并且它们都有 __del__
方法,那么这些对象就不会被垃圾回收。这是因为垃圾回收器不知道应该先调用哪个方法。想了解更多,可以查看 gc 模块的文档。
我之前写了一个回答,虽然这个问题更准确。
这里有一个稍微带有个人观点的回答。
不要使用 __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__
意味着你不会忘记调用关闭语句。可以查看 http://eli.thegreenplace.net/2009/06/12/safely-using-destructors-in-python/ 来了解支持__del__
的观点。这通常是关于释放 ctypes 或其他特殊资源。
还有我个人不喜欢 __del__
函数的原因。
- 每次有人提到
__del__
,讨论就会变成三十条混乱的信息。 - 它违反了 Python 的一些原则:
- 简单比复杂好。
- 特殊情况不够特殊,不应该打破规则。
- 错误不应该悄悄溜走。
- 面对模糊性,拒绝猜测的诱惑。
- 应该有一种——最好只有一种——明显的方法来做到这一点。
- 如果实现难以解释,那就是个坏主意。
所以,找个理由不要使用 __del__
。
__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 也可能会改变。