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


108

我正在阅读代码。有一个__del__定义方法的类。我发现此方法用于销毁该类的实例。但是,我找不到使用此方法的地方。这样做的主要原因是我不知道如何使用此方法,可能不是这样:obj1.del()。那么,我的问题是如何调用该__del__方法?

Answers:


168

__del__终结器。当删除对象的所有引用之后的某个时刻发生垃圾回收时,调用该方法。

在一个简单的例子中,这可能是在您说完之后,del x或者如果x是局部变量,则在函数结束之后。特别是,除非有循环引用,否则CPython(标准Python实现)将立即进行垃圾回收。

但是,这是CPython 的实现细节。Python垃圾回收的唯一必需属性是,它会删除所有引用之后发生,因此这可能之后没有必要发生可能根本没有发生

此外,由于多种原因,变量可以生存很长一段时间,例如,传播异常或模块自省可以使变量引用计数保持大于0。此外,变量可以是引用循环的一部分-启用垃圾回收的CPython多数会中断,但不是全部,这样的周期,甚至只是周期性的。

由于您无法保证它会被执行,因此永远不要将您需要运行的代码放入其中__del__()-而是,该代码属于块的finally子句trywith语句中的上下文管理器。不过,也有有效的使用情况__del__:例如,如果一个对象X的引用Y,也保留副本Y参考在全球cachecache['X -> Y'] = Y),那么这将是一个有礼貌X.__del__也删除缓存条目。

如果您知道析构函数提供了必要的清除操作(违反了上述准则),则您可能希望直接调用它,因为该方法没有什么特别之处:x.__del__()。显然,仅当您知道不介意被两次调用时,才应该这样做。或者,作为最后的选择,您可以使用重新定义此方法

type(x).__del__ = my_safe_cleanup_method  

5
您说CPython在其引用计数减少为零后立即删除对象的功能是“实现细节”。我不相信。您能否提供备份该声明的链接?(我的意思是,加粗字体本身很令人信服,但链接紧随其后... :-)
Stuart Berg

14
CPython实现细节: CPython当前使用引用计数方案,并(可选)延迟检测循环链接的垃圾,...其他实现的行为有所不同,CPython可能会发生变化。(docs.python.org/2/reference/datamodel.html
伊利亚安德ñ。

有什么__exit__在这方面?它是在运行之前,之后__del__还是一起运行?
lony

1
程序终止时是否包括“可能根本不会发生”?
安迪·海登

1
@AndyHayden:__del__方法即使在程序终止时也可能无法运行,即使它们确实在终止时运行,编写一种__del__即使在解释器忙于自毁时仍能正常工作的方法,需要比许多程序员更仔细地编码。(CPython清理通常使__del__方法在解释器关闭时运行,但在某些情况下还不够。守护进程线程,C级全局变量以及__del__在另一个__del____del__
实例中

80

我写下了另一个问题的答案,尽管这是一个更准确的问题。

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

这是一个有点自以为是的答案。

不要使用__del__。这不是C ++或为析构函数构建的语言。该__del__方法确实应该在Python 3.x中消失,尽管我确信有人会发现一个有意义的用例。如果您需要使用__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- 德尔 -is-不是最相反OF- 的init /一个解释和陷阱。
  • __del__在Python中不是一个“受欢迎的”孩子。您会注意到sys.exit()文档没有指定是否在退出之前收集垃圾,并且存在很多奇怪的问题。调用__del__on全局变量会导致奇怪的排序问题,例如http://bugs.python.org/issue5099__del__即使__init__失败也应该打电话吗?有关长线程的信息,请参见http://mail.python.org/pipermail/python-dev/2000-March/thread.html#2423

但另一方面:

我不喜欢该__del__功能的原因。

  • 每当有人提出来时,__del__它就会演变成三十种混乱的信息。
  • 它在Python的Zen中打破了这些项目:
    • 简单胜于复杂。
    • 特殊情况不足以违反规则。
    • 错误绝不能默默传递。
    • 面对模棱两可的想法,拒绝猜测的诱惑。
    • 应该有一种(最好只有一种)明显的方式来做到这一点。
    • 如果实现难以解释,那是个坏主意。

因此,找到不使用的理由__del__


6
即使问题不完全是:为什么我们不应该使用__del__,而是如何调用__del__,您的回答却很有趣。
nbro 2015年

谢谢。有时最好的主意是避免可怕的主意。
Charles Merriam 2015年

在其他新闻中,我忘记提及PyPy(运行时间更长的应用程序的更快的解释器)将在del上中断。
Charles Merriam

感谢@Gloin更新断开的链接!
Charles Merriam

@CharlesMerriam谢谢的回答!
汤姆·伯罗斯

13

__del__方法将在垃圾回收对象时被调用。请注意,不一定要调用它。以下代码本身不一定会这样做:

del obj

原因是del只是将引用计数减一。如果还有其他对象引用,__del__则不会调用。

__del__尽管有一些注意事项。通常,它们通常不是很有用。在我看来,这更像是您要使用close方法或with语句

请参阅有关__del__方法python文档

需要注意的另一件事: __del__如果使用过多的方法,可能会阻止垃圾回收。特别是,使用一种__del__方法具有多个对象的循环引用将不会收集垃圾。这是因为垃圾收集器不知道首先调用哪个。有关更多信息,请参见gc模块上的文档。


8

__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
>>>   

请注意,关于删除和__del__调用对象的确切时间,jython和ironpython具有不同的规则。__del__由于这个原因以及调用对象和其环境可能处于未知状态这一事实,因此不认为使用该方法是一种好习惯。也不绝对保证__del__会被调用-解释器可以以各种方式退出而不删除所有对象。


1
相比stackoverflow.com/a/2452895/611007stackoverflow.com/a/1481512/611007use del obj1似乎是一个坏主意的依靠。
n611x007 2013年

0

如前所述,该__del__功能有些不可靠。在看起来有用的情况下,请考虑使用__enter__and __exit__方法。这将产生类似于with open() as f: pass用于访问文件的语法的行为。__enter__进入时with__exit__会自动调用,退出时会自动调用。有关更多详细信息,请参见此问题

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.