__del__的用例

max*_*max 14 python destructor python-3.x

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

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

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

更新:

PEP 442在行为上做出了重大改进__del__.虽然我的第1-4点仍然有效?


更新2:

一些顶级的python库包含__del__在后PEP 442 python(即python 3.4+)中的使用.我想我的观点3在PEP 442之后不再有效,其他点被接受为对象终结的不可避免的复杂性.


1我将问题从编写自定义__del__方法扩展到包括依赖于__del__stdlib.

2似乎__del__总是在更新版本的Cpython中调用解释器退出(有没有人有反例?).然而,对于__del__可用性的目的并不重要:文档明确地不提供对此行为的保证,因此不能依赖它(它可能在将来的版本中发生变化,在非CPython解释器中可能会有所不同) .

ali*_*i_m 8

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

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


MSe*_*ert 7

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

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

当你包装一个外部对象(例如一个没有被Python跟踪的对象)的时候,即使有人"忘记"(我怀疑很多人只是故意省略它们),真的需要释放它们才能使用您提供的上下文管理器.


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

如果你想知道你是否需要它们[metaclasses],你就不会(实际需要它们的人确切地知道他们需要它们,并且不需要解释为什么).

引用(可能)来自蒂姆彼得斯(我没有找到原始参考).