__del __

时间:2017-04-27 19:52:17

标签: python python-3.x destructor

在python 3中编写自定义__del__方法或依赖stdlib 1 中的一个用例是什么?也就是说,它在什么情况下是相当安全的,并且可以做一些没有它的事情很难做到的事情?

出于很多好的理由(1 2 3 4 5 6),通常的建议是避免{{1}而是使用上下文管理器或手动执行清理:

    如果对象在intrepreter exit 2 上处于活动状态,则无法保证
  1. __del__被调用。
  2. 在点1期望对象可以被破坏时,引用计数实际上可以是非零的(例如,引用可以通过由调用函数保持的回溯帧而存活)。这使得破坏时间远不如__del__所暗示的那么不可预测。
  3. 垃圾收集器如果包含多个具有gc
  4. 的对象,则无法摆脱循环
  5. __del__内的代码必须仔细编写:
    • __del__中设置的对象属性可能不存在,因为__init__可能引发了异常;
    • 忽略异常(仅打印到__init__);
    • 全局变量可能不再可用。
  6. 更新

    PEP 442stderr的行为进行了重大改进。虽然我的第1-4点仍然有效?

    更新2:

    后PEP 442 python中的一些顶级python库embrace the use of __del__(即python 3.4+)。我想我的观点3在PEP 442之后不再有效,其他点被认为是对象终结的不可避免的复杂性。

    1 我将问题从仅编写自定义__del__方法扩展到包括依赖stdlib中的__del__

    2 似乎__del__总是在更新版本的Cpython中调用解释器出口(有没有人有反例?)。但是,对于__del__的可用性而言,这并不重要:文档明确provide no guarantee about this behavior,因此不能依赖它(它可能会在未来的版本中发生变化,并且可能在非-CPython口译员。)

3 个答案:

答案 0 :(得分:8)

上下文管理员(以及try / finally块)比__del__更具限制性。通常,它们要求您构造代码,使得您需要释放的资源的生命周期不超出调用堆栈中某个级别的单个函数调用,而不是将其绑定到生命周期可以在不可预测的时间和地点销毁的类实例。将资源的生命周期限制在一个范围内通常是一件好事,但有时会出现这种模式难以适应的边缘情况。

我使用__del__的唯一情况(除了调试,c.f。@ MSeifert的答案)是用于释放外部库在Python外部分配的内存。由于我正在包装的库的设计,很难避免有大量的对象持有指向堆分配的内存的指针。使用__del__方法释放指针是最简单的清理方法,因为将每个实例的生命周期封装在上下文管理器中是不切实际的。

答案 1 :(得分:7)

一个用例是调试。如果要跟踪特定对象的生命周期,可以方便地编写临时 __del__方法。它可用于执行某些日志记录或仅用于print某些内容。我已经使用了几次,特别是当我对何时以及如何删除实例感兴趣时。知道何时创建和丢弃大量临时实例有时会很好。但正如我所说,我只是用它来满足我的好奇心或调试时。

另一个用例是子类化定义__del__方法的类。有时您会发现要创建子类的类,但内部要求您实际覆盖__del__以控制清除实例的顺序。这也是非常罕见的,因为你需要找到一个__del__的类,你需要将它子类化,你需要引入一些内部实际上需要在恰当的时间调用超类__del__。我实际上曾经这样做了一次,但我不记得它在哪里以及为什么它很重要(也许我当时甚至不知道替代品,所以将其视为可能的用例)。

当你包装一个外部对象(例如一个没有被Python跟踪的对象)时,真的,真的需要被解除分配,即使有人“忘记”(我怀疑很多人只是故意省略它们!)使用你提供的上下文管理器。

然而,所有这些情况都是(或应该)非常非常罕见。实际上它有点像元类:它们很有趣,理解概念真的很酷,因为你可以探索python的“有趣部分”。但在实践中:

  

如果你想知道你是否需要它们[元类],你就不会(真正需要它们的人确切地知道他们需要它们,并且不需要解释原因)。

引用(可能)来自Tim Peters(我没有找到原始参考文献)。

答案 2 :(得分:1)

我总是使用__del__的一种情况是关闭aiohttp.ClientSession对象。

当您不知道时,aiohttp将打印有关未公开的客户会话的警告。