17

来自python 文档

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

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

4

5 回答 5

7

I'm not convinced by the previous answers here.

Firstly note that the example given does not prevent __del__ methods being called during exit. In fact, the current CPythons will call the __del__ method given, twice in the case of Python 2.7 and once in the case of Python 3.4. So this can't be the "killer example" which shows why the guarantee is not made.

I think the statement in the docs is not motivated by a design principle that calling the destructors would be bad. Not least because it seems that in CPython 3.4 and up they are always called as you would expect and this caveat seems to be moot.

Instead I think the statement simply reflects the fact that the CPython implementation has sometimes not called all destructors on exit (presumably for ease of implementation reasons).

The situation seems to be that CPython 3.4 and 3.5 do always call all destructors on interpreter exit.

CPython 2.7 by contrast does not always do this. Certainly __del__ methods are usually not called on objects which have cyclic references, because those objects cannot be deleted if they have a __del__ method. The garbage collector won't collect them. While the objects do disappear when the interpreter exits (of course) they are not finalized and so their __del__ methods are never called. This is no longer true in Python 3.4 after the implementation of PEP 442.

However, it seems that Python 2.7 also does not finalize objects that have cyclic references, even if they have no destructors, if they only become unreachable during the interpreter exit.

Presumably this behaviour is sufficiently particular and difficult to explain that it is best expressed simply by a generic disclaimer - as the docs do.

Here's an example:

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

With the del b commented out, the destructor in Foo is not called in Python 2.7 though it is in Python 3.4.

With the del b added, then the destructor is called (at interpreter exit) in both cases.

于 2015-10-13T12:52:43.170 回答
4

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

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

a = Phoenix()

在任何情况下,依赖__del__都不是很好,因为 python 不能保证何时删除对象(尤其是具有循环引用的对象)。也就是说,也许将你的类变成上下文管理器是一个更好的解决方案......然后你可以保证即使在出现异常等情况下也会调用清理代码......

于 2013-01-31T14:52:17.920 回答
1

我不认为这是因为删除会导致问题。更多的是 Python 的哲学不是鼓励开发人员依赖使用对象删除,因为这些删除的时间是无法预测的——这取决于垃圾收集器何时发生。

如果垃圾收集器可能会在未使用的对象超出范围后将其延迟删除一段未知的时间,那么依赖于对象删除期间发生的副作用不是一个非常健壮或确定性的策略。RAII不是 Python 的方式。相反,Python 代码使用上下文管理器、装饰器等来处理清理工作。

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

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

于 2016-08-12T17:13:42.673 回答
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
于 2018-01-12T16:55:50.530 回答
0

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

如果开发人员没有预料到对活动对象的析构函数调用,可能会导致一些讨厌的 UB。至少,如果应用程序挂起,则必须在超时后强制关闭应用程序。但是可能不会调用一些析构函数。

由于同样的原因,Java Runtime.runFinalizersOnExit 已被弃用。

于 2013-01-31T14:55:44.677 回答