为什么解释器退出时不保证调用析构函数?

23 投票
5 回答
7496 浏览
提问于 2025-04-17 14:19

来自Python文档的内容:

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

为什么不这样保证呢?如果这样保证会出现什么问题呢?

5 个回答

1

我觉得这并不是因为删除对象会引发问题。更主要的是,Python的设计理念是不鼓励开发者依赖对象删除,因为删除的时机是无法预测的——这完全取决于垃圾回收器什么时候去做。

如果垃圾回收器可能会在对象超出作用域后,延迟一段不确定的时间才删除那些未使用的对象,那么依赖于对象删除时发生的副作用就不是一个很可靠或可预测的做法。RAII(资源获取即初始化)并不是Python的做法。相反,Python的代码通常通过上下文管理器、装饰器等方式来处理清理工作。

更糟糕的是,在一些复杂的情况下,比如对象之间有循环引用,垃圾回收器可能根本无法发现这些对象可以被删除。随着Python的发展,这种情况有所改善。但由于像这样的垃圾回收行为的例外情况,Python开发者依赖对象删除是不明智的。

我猜测,解释器退出也是一个复杂的情况,尤其是在较旧版本的Python中,开发者并没有严格确保垃圾回收器会对所有对象进行删除。

5

如果你做了一些不太好的事情,你可能会遇到一个无法删除的对象,Python会一直尝试删除它:

class Phoenix(object):
    def __del__(self):
        print "Deleting an Oops"
        global a
        a = self

a = Phoenix()

依赖于 __del__ 这个方法并不是一个好主意,因为Python并不能保证一个对象会在什么时间被删除(尤其是那些有循环引用的对象)。不过,把你的类变成一个上下文管理器可能是个更好的解决办法……这样即使在出现异常的情况下,你也能确保清理代码会被执行等等……

10

我对之前的回答不太信服。

首先要注意,给出的例子并没有阻止在程序退出时调用__del__方法。实际上,目前的CPython在Python 2.7中调用两次__del__方法,而在Python 3.4中调用一次。所以这个例子并不能说明为什么没有保证。

我认为文档中的说法并不是因为设计原则认为调用析构函数不好。尤其是因为在CPython 3.4及以上版本中,它们总是会按预期被调用,这个警告似乎就没什么意义了。

相反,我觉得这个说法只是反映了CPython的实现有时在退出时没有调用所有的析构函数(可能是为了实现上的方便)。

目前的情况是,CPython 3.4和3.5在解释器退出时总是会调用所有的析构函数。

而CPython 2.7则不一定会这样做。确实,__del__方法通常不会在有循环引用的对象上被调用,因为如果这些对象有__del__方法,它们就无法被删除。垃圾回收器不会收集它们。虽然这些对象在解释器退出时会消失(当然),但它们并没有被最终处理,因此它们的__del__方法也不会被调用。在Python 3.4中,这种情况在实现了PEP 442后就不再成立了。

不过,似乎Python 2.7也不会最终处理那些有循环引用的对象,即使它们没有析构函数,只要它们在解释器退出时变得不可达。

可以推测,这种行为足够特殊且难以解释,因此最好用一个通用的免责声明来表达——就像文档中所说的那样。

这里有个例子:

class Foo(object):
    def __init__(self):
        print("Foo init running")

    def __del__(self):
        print("Destructor Foo")

class Bar(object):
    def __init__(self):
        print("Bar1 init running")
        self.bar = self
        self.foo = Foo()

b = Bar()

# del b

在Python 2.7中,如果注释掉del bFoo中的析构函数不会被调用,而在Python 3.4中会被调用。

如果加上del b,那么在两种情况下,析构函数都会在解释器退出时被调用。

撰写回答