【问题标题】:What is the __del__ method and how do I call it?什么是 __del__ 方法,我该如何称呼它?
【发布时间】:2010-12-01 16:13:17
【问题描述】:

我看到一个定义了__del__ method 的类。此方法用于销毁类的实例。但是,我找不到使用这种方法的地方。这种方法是如何使用的?像这样:obj1.del()?.

如何调用__del__ 方法?

【问题讨论】:

    标签: python oop


    【解决方案1】:

    __del__ 是一个终结器。当一个对象被垃圾收集时调用它,这发生在对该对象的所有引用都被删除之后的某个时刻。

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

    不过,这是 CPython 的实现细节。 Python 垃圾回收的唯一必需属性是它发生在所有引用都被删除之后,所以这可能没有必要之后发生并且可能根本不会发生。

    更重要的是,由于许多原因,变量可以长期存在,例如传播异常或模块自省可以使变量引用计数保持大于 0。此外,变量可以是引用循环的一部分——打开垃圾收集的 CPython 会中断大多数(但不是全部)这样的循环,即便如此,也只是周期性的。

    既然你不能保证它会被执行,那么你应该永远不要将你需要运行的代码放入__del__()——相反,这段代码属于tryfinally子句在with 语句中阻止或发送到上下文管理器。但是,__del__有效用例:例如如果对象X 引用Y 并且还在全局cache (cache['X -> Y'] = Y) 中保留Y 引用的副本,那么X.__del__ 也删除缓存条目是礼貌的。

    如果您知道析构函数提供了(违反上述准则)所需的清理,您可能希望直接调用它,因为没有什么特别之处它作为一种方法:x.__del__()。显然,只有当你知道它可以被调用两次时,你才应该这样做。或者,作为最后的手段,您可以使用重新定义此方法

    type(x).__del__ = my_safe_cleanup_method
    

    【讨论】:

    • 你说CPython的在引用计数减少到零后立即删除对象的特性是一个“实现细节”。我不相信。您能否提供支持该声明的链接? (我的意思是,粗体字 本身就很有说服力,但链接紧随其后...... :-)
    • CPython 实现细节: CPython 目前使用引用计数方案,带有(可选)延迟检测循环链接垃圾,...其他实现的行为不同,CPython 可能会改变。 (docs.python.org/2/reference/datamodel.html)
    • “可能根本不会发生”是否包括程序终止的时间?
    • @AndyHayden: __del__ 方法即使在程序终止时也可能不会运行,即使它们确实在终止时运行,编写一个 __del__ 方法即使在解释器忙于自毁时也能正常工作你身边的人需要比许多程序员更仔细的编码。 (CPython 清理通常会在解释器关闭时运行 __del__ 方法,但仍然存在不够的情况。守护线程、C 级全局变量和在另一个 __del__ 中创建的 __del__ 对象都可以导致到__del__ 方法未运行。)
    • @bombs 它不是析构函数,原因与__init__ 不是构造函数相同。没有人实际创建或销毁对象。 __init__ 方法初始化一个在被调用时已经存在的对象(这就是为什么它也不返回任何东西的原因)。同样,在__del__ 退出时对象仍然存在:它只是在实际销毁发生之前进行任何可能需要的清理(“终结”)。
    【解决方案2】:

    如前所述,__del__ 功能有些不可靠。如果它看起来有用,请考虑改用__enter____exit__ 方法。这将提供类似于用于访问文件的with open() as f: pass 语法的行为。进入with范围时自动调用__enter__,退出时自动调用__exit__。详情请见this question

    【讨论】:

      【解决方案3】:

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

      How do constructors and destructors work?

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

      不要使用__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-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__ 时,都会演变成 30 条令人困惑的消息。
      • 它打破了 Python 之禅中的这些项目:
        • 简单胜于复杂。
        • 特殊情况不足以打破规则。
        • 错误绝不应该悄无声息地过去。
        • 面对模棱两可,拒绝猜测的诱惑。
        • 应该有一种——最好只有一种——明显的方法。
        • 如果实现难以解释,那就是个坏主意。

      所以,找个理由不使用__del__

      【讨论】:

      • 即使问题不完全是:为什么我们不应该使用__del__,而是如何调用__del__,你的回答很有趣。
      • 在其他新闻中,我忘了提到 PyPy(对于运行时间较长的应用程序的更快解释器)将在 del 上中断。
      • @CharlesMerriam 谢谢的回答!
      • 您提到“del 在垃圾收集器碰巧收集对象时调用,而不是在您丢失对对象的最后一个引用时调用”。但是,文档明确指出:“注意:del x 不直接调用 x.__del__() ——前者将 x 的引用计数减一,而后者仅在 x 的引用计数达到零时调用”。所以当你失去对对象的最后一个引用时,总是会调用 del
      • 恐怕比这更随机。没有丢失最后一个引用的原因有很多,而且很多都是暂时的,这使得推理和调试变得困难。并且在实现上存在差异,并且有硬停止,并且..和...和...尝试识别贪婪的bugblatter野兽并用毛巾避开它。
      【解决方案4】:

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

      del obj
      

      原因是del 只是将引用计数减一。如果其他东西引用了该对象,__del__ 将不会被调用。

      不过,使用__del__ 有一些注意事项。通常,它们通常不是很有用。在我看来,您更像是想使用 close 方法或者with statement

      请参阅python documentation on __del__ methods

      还有一点需要注意:__del__ 方法如果过度使用会抑制垃圾收集。特别是,具有多个具有__del__ 方法的对象的循环引用不会被垃圾回收。这是因为垃圾收集器不知道先调用哪个。有关详细信息,请参阅gc module 上的文档。

      【讨论】:

        【解决方案5】:

        当你的对象最终被销毁时,会调用__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__ 会被调用 - 解释器可以以各种方式退出而不删除所有对象。

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-02-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-18
        • 2011-07-16
        • 1970-01-01
        相关资源
        最近更新 更多