【问题标题】:RAII in Python: What's the point of __del__?Python 中的 RAII:__del__ 有什么意义?
【发布时间】:2015-02-03 14:10:06
【问题描述】:

乍一看,Python 的 __del__ 特殊方法似乎提供了与 C++ 中的析构函数大致相同的优点。但是根据 Python 文档 (https://docs.python.org/3.4/reference/datamodel.html),无法保证您的对象的 __del__ 方法会被调用!

不能保证在解释器退出时为仍然存在的对象调用__del__() 方法。

也就是说,方法是没用的!不是吗?一个可能会或可能不会被调用的钩子函数真的没有多大用处,所以__del__ 没有提供任何关于 RAII 的信息。如果我有一些必要的清理工作,我不需要它在某些时间运行,哦,当 GC 感觉真的如此时,我需要它运行 可靠、确定性和100% 的时间

我知道 Python 提供了上下文管理器,这对于该任务更有用,但为什么要保留 __del__ 呢?有什么意义?

【问题讨论】:

  • 我真的不明白你到底在问什么。你的问题是“__del__ 基本上没用吗?”? (我相信这是您的问题)在这种情况下,我相信您会通过删除所有对 C++ 或 RAII 的引用来澄清这个问题,这些引用完全不相关。如果您的问题是“如何在 Python 中实现 RAII?我可以为此使用 __del__ 吗?”那么您可能需要更改问题中的很多内容。
  • @Bakuriu 问题的关键是,'__del__' 到底有什么意义?有人可以为该方法命名一个实际的用例吗?

标签: python raii


【解决方案1】:

__del__ 是一个终结器。它不是析构函数。终结器和析构函数是完全不同的动物。

析构函数被可靠地调用,并且只存在于具有确定性内存管理的语言中(例如 C++)。 Python 的上下文管理器(with 语句)在某些情况下可以实现类似的效果。这些是可靠的,因为对象的寿命是精确固定的;在 C++ 中,当对象显式为 deleted 或退出某个范围时(或当智能指针删除它们以响应自身的破坏时),对象就会死亡。这就是析构函数运行的时候。

终结器的调用不可靠。终结器的唯一有效用法是 an emergency safety net(注意:本文是从 .NET 角度编写的,但概念翻译得相当好)。例如,open() 返回的文件对象在完成时会自动关闭。但是您仍然应该自己关闭它们(例如使用with 语句)。这是因为对象是由垃圾收集器动态销毁的,它可能会或可能不会立即运行,并且使用分代垃圾收集,它可能会或可能不会在任何给定的传递中收集一些对象。由于没有人知道我们将来可能会发明什么样的优化,因此最安全的假设是您不知道垃圾收集器何时会收集您的对象。这意味着你不能依赖终结器。

在 CPython 的特定情况下,由于使用了引用计数(这比垃圾回收更简单且更可预测),您可以获得稍强的保证。如果您可以确保永远不会创建涉及给定对象的引用循环,则该对象的终结器将在可预测的点(当最后一个引用终止时)被调用。这适用于 CPython、参考实现,适用于 PyPy、IronPython、Jython 或任何其他实现。

【讨论】:

  • 您链接到的页面上的一个引号是 > 正确编写的程序不能假设终结器会运行。这只是加强了我的信念,即它们本质上是无用的。即使您说它们可以用作紧急安全网来尝试清理程序员忘记的东西并没有真正改变这一点,因为它可能再次发生,也可能不会发生。无论如何,我认为这是一个可怕的想法。它只会造成一种错误的安全感,造成混乱并鼓励草率的资源管理(嘿,我不必清理,Python 可能只为我做)。
  • 我碰巧同意你的看法。这就是我从不使用终结器的原因。 OTOH,附加到弱引用的终结器有时会很有用,例如,如果您需要维护活动对象的集合但不想让它们保持活动状态。请参阅weakref 了解更多信息。
  • PyPy 确实保证您的 del 会被调用。在 Cpython 中,如果它是循环的一部分,则根本不会调用它。
  • @fijal:从 3.4+ 起不再适用。请参阅 PEP 442。此外,PyPy 确实保证调用,但不是在可预测的时间,因此对于大多数目的来说它是不够的。
  • 是的,我知道那个 PEP。在大多数情况下,del 确实是个坏主意,不要使用它。 CPython 也不保证只有一次调用(如果复活,del 将被重复调用)。我发现“调用一次或多次,可能是直接调用,但在循环的情况下不完全调用”也不是充分的保证:-)
【解决方案2】:

因为__del__ 确实被调用了。只是不清楚何时会,因为在 CPython 中,如果您有循环引用,则引用计数机制无法处理对象回收(因此通过__del__ 完成它)并且必须将其委托给垃圾收集器。

然后垃圾收集器有一个问题:他不知道以什么顺序破坏循环引用,因为这可能会引发额外的问题(例如,释放在终结另一个对象时将需要的内存,该对象是收集到的循环,触发段错误)。

您强调的一点是,解释器可能会因为阻止它执行清理的原因而退出(例如,它的段错误,或者某些 C 模块不礼貌地调用 exit() )。

安全对象终结的 PEP 442 已在 3.4 中终结。我建议你看看它。

https://www.python.org/dev/peps/pep-0442/

【讨论】:

  • 这再次表明__del__ 是多么无用。例如,如果我在哪里使用互斥守卫类的对象,我无法在守卫类的 __del__ 方法中进行解锁,因为仅仅知道__del__ 将在 GC 感觉时被调用是不够的做它的工作......我需要在保护对象“超出范围”时立即释放互斥锁(我知道这是在拉伸它,考虑到 Python 的范围规则)。 IMO __del__ 给程序员一种错误的印象,即 Python 中的 RAII 可以通过析构函数实现,而实际上并非如此。为此,您必须使用上下文管理器。
  • 不是没用的。您正在尝试在 python 中编写 C++ 代码。关键是__del__ 不是析构函数。这是一种已经到位的机制,可能在非常有限的情况下有用,但你不能依赖它,类似于 java 终结器。有关类似问题,请参阅此主题stackoverflow.com/questions/158174/…
猜你喜欢
  • 2018-11-21
  • 2015-02-03
  • 2010-11-04
  • 2010-09-15
  • 2022-07-31
  • 2020-04-17
  • 2011-05-22
  • 2015-01-17
  • 1970-01-01
相关资源
最近更新 更多