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

时间:2013-01-31 14:46:45

标签: python destructor

来自python docs

  

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

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

5 个答案:

答案 0 :(得分:7)

我不相信以前的答案。

首先请注意,给出的示例 not 阻止在退出期间调用__del__方法。事实上,当前的CPythons 调用给定的__del__方法,在Python 2.7的情况下调用两次,在Python 3.4的情况下调用一次。所以这不能成为“杀手的例子”,它说明了为什么没有保证。

我认为文档中的陈述不是出于设计原则的动机,即调用析构函数会很糟糕。尤其是因为似乎在CPython 3.4及以上版本中,它们总是被调用,正如您所期望的那样,这个警告似乎没有实际意义。

相反,我认为该语句只是反映了这样一个事实:CPython实现有时并没有在退出时调用所有析构函数(大概是为了便于实现)。

情况似乎是CPython 3.4和3.5总是在解释器退出时调用所有析构函数。

相比之下,CPython 2.7并不总是如此。当然,通常不会对具有循环引用的对象调用__del__方法,因为如果这些对象具有__del__方法,则无法删除这些对象。垃圾收集器不会收集它们。当解释器退出时,对象确实消失了(当然)它们没有最终确定,因此永远不会调用它们的__del__方法。在PEP 442实现后,在Python 3.4中不再适用。

然而,似乎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

del b注释掉了Foo中的析构函数在Python 2.7中没有被调用,尽管它在Python 3.4中。

添加del b后,在两种情况下都会调用析构函数(在解释器出口处)。

答案 1 :(得分:4)

如果你做了一些讨厌的事情,你可能会发现自己有一个不可删除的对象,python会试图永久删除:

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

a = Phoenix()

依赖于__del__在任何情况下都不是很好,因为当某个对象被删除时(特别是带有循环引用的对象),python不能保证。也就是说,或许将您的类转换为上下文管理器是一个更好的解决方案......然后您可以保证即使在异常等情况下也会调用清理代码......

答案 2 :(得分:1)

我不认为这是因为删除会导致问题。更重要的是,Python的理念不是鼓励开发人员依赖于对象删除的使用,因为无法预测这些删除的时间 - 它发生时会由垃圾收集器决定。

如果垃圾收集器在超出范围后推迟删除未使用的对象一段时间,那么依赖于删除对象期间发生的副作用并不是一种非常强大或确定的策略。 RAII不是Python方式。相反,Python代码使用上下文管理器,装饰器等来处理清理。

更糟糕的是,在复杂的情况下,例如对象周期,垃圾收集器可能永远不会检测到可以删除对象。随着Python的成熟,这种情况有所改善。但是由于像这样的预期GC行为的例外,Python开发人员依赖于对象删除是不明智的。

我推测解释器退出是另一个复杂的情况,其中Python开发人员,特别是对于旧版本的Python,并不完全严格确保GC删除在所有对象上运行。

答案 3 :(得分:1)

如果您在方法内退出,则不会调用析构函数的一个示例。看看这个例子:

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()

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

    def stop(self):
        del self.foo
        del self
        exit(1)

b = Bar()
b.stop()

输出结果为:

Bar1 init running
Foo init running
Destructor Foo

当我们明确地描述foo时,析构函数被调用,但不是bar的析构函数!

而且,如果我们没有明确删除foo,它也没有被正确删除:

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()

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

    def stop(self):
        exit(1)

b = Bar()
b.stop()

输出:

Bar1 init running
Foo init running

答案 4 :(得分:0)

可能因为大多数程序员都认为只应该在死(已经无法访问)的对象上调用析构函数,并且在退出时我们会在活动对象上调用它们。

如果开发人员没有期待对活动对象进行析构函数调用,则可能会导致一些讨厌的UB。至少,如果应用程序挂起,必须采取措施强制关闭应用程序。但是有些析构函数可能不会被调用。

由于同样的原因,不推荐使用Java Runtime.runFinalizersOnExit。