Python是否总是同步调用__del__方法?

3
Python文档中__del__的解释说明当对象的引用计数降为零时,可以调用其__del__方法。它是否总是立即调用(即同步)?或者__del__方法可能在“将来的某个时间点”被调用?
此外,这个问题的答案适用于Python吗?还是语言实现允许一些余地?(我使用CPython。)
对于这个问题,请假设以下内容:
- 有关对象继承自object。 - 有关对象具有自定义的__del__实现。 - 有关对象的自定义__del__实现不会创建对任何对象的新引用。 - 有关对象不是引用循环的一部分。 - 解释器没有关闭的过程。
据我所知,这个话题似乎存在很多混淆。为了避免一些不必要的讨论,让我举几个不可接受的答案示例:
  • 不要使用__del__!这会让人感到困惑!
  • 不要使用__del__!这不符合Python的风格。
  • 不要使用__del__,应该使用上下文管理器。
  • 任何断言有关于什么是或不是保证而没有引用可靠来源的答案。(请随意引用其他SO答案,但不要将其作为唯一的来源。在这种情况下,“可靠”意味着来自文档、权威人士或甚至是CPython源代码本身的东西。)

为了用一个例子来重新表述这个问题,请考虑以下代码:

class C(object):
    def __init__(self, i):
        self.i = i

    def __del__(self):
        print "C.__del__:", self.i

print "Creating list"
l = [C(0), C(1), C(2)]

print "Popping item 1"
l.pop(1)

print "Clearing list"
l = []

print "Exiting..."

这将产生以下输出:

$ python test_del.py
Creating list
Popping item 1
C.__del__: 1
Clearing list
C.__del__: 2
C.__del__: 0
Exiting...

注意,在这个例子中,__del__是同步调用的。这种行为是否由语言保证?(请记住上面列出的假设。)

你的使用场景是什么,答案很重要吗? - Andrew Jaffe
如果我可以依赖这种行为,它将允许更清晰的API设计。而不是强制我的库用户记住在我给他们的对象上调用一些特殊的“cleanUp()”函数,我可以只需将必要的清理代码放在__del__中。例如,在C++中,这就是基于RAII的良好API设计的本质。 - Stuart Berg
1
但是,知道何时调用__del__会改变什么呢?(对象无论如何都将在某个时候被删除) - Andrew Jaffe
一个公平的问题; 感谢您提出它。在我的代码中,我现在有一个断言,可以捕获某些对象“被遗忘”的清理。这并不是严格必要的;它只是为了捕捉程序员的错误(“你忘记了什么吗?”)。如果我知道该对象应该已经被清理了,那么这是一个有效的检查。但是,如果该对象可能合法地存在于某个“待办事项”列表中,那么我不能通过断言来责备程序员没有清理它。 - Stuart Berg
2个回答

3

来自 Python 语言参考手册 (Python 2.7.3 版本),第三章,数据模型

对象永远不会被显式地销毁;但是,当它们变得无法访问时,它们可能会被垃圾回收。实现可以推迟垃圾回收或完全省略 —— 垃圾回收的实现质量是如何实现的问题,只要不回收仍然可访问的对象。

CPython 实现细节: CPython 目前使用引用计数方案,并采用(可选的)延迟检测循环引用垃圾收集,尽可能在大多数对象变得不可访问时立即收集,但不能保证收集包含循环引用的垃圾。有关控制循环垃圾收集的信息,请参阅 gc 模块的文档。其他实现行为不同,CPython 可能会更改。不要依赖于对象在变得不可访问时立即完成最终化(例如:始终关闭文件)。


唉,我怎么会错过那个?我花了时间阅读关于__del__本身的文档,但它并没有明确说明什么是实现定义。它只说__del__被调用“当x的引用计数达到零时”。但你引用的段落清晰明了地解释了这一点。 - Stuart Berg
接受此答案,但请您也看一下@Andrew的答案(下面)中非常相关的关于PyPy的链接。 - Stuart Berg

1

语言并不保证行为。例如,请参阅PyPy垃圾回收方案的文档,其中明确说明在PyPy中调用__del__的时间与CPython不同。

如果您的对象不是引用循环的一部分,则我理解在CPython中此行为是可靠的,但我不知道是否有任何明确的保证。


很棒的链接,谢谢。它给了我三个印象:(1)语言规范含糊不清,但似乎暗示着立即删除行为是被期望的。(2)那是一个任意的决定,可能受到CPython实现的影响。(3)实际上,“官方”答案是什么并不重要,因为PyPy、Jython和IronPython的作者不会采用CPython的引用计数方案。这个事实意味着__del__行为是实际上“未定义”的,无论标准撰写者是否关心。 - Stuart Berg
我不能确定语言规范在这一点上是否不清楚,但从实际角度来看,我同意这种行为是未定义的,除非在您真正希望代码在对象从内存中删除时运行的情况下使用它(这似乎是一个不太可能的用例)。 - Andrew Gorcester
另外,还有一点需要注意:即使CPython是唯一的目标,我认为编写代码时不应假设一个对象不是引用循环的一部分。一个对象可能会因为与对象本身无关的外部情况而成为引用循环的一部分。 - Andrew Gorcester

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接