python - 在 Python 中,每个对象可以调用 `__del__` 多少次?

标签 python garbage-collection destructor

我看到一些代码,其中 __del__ 在我觉得很好奇的对象上被显式调用,所以我试着用它来了解它是如何工作的。

我尝试了以下代码(我知道使用 __del__ 本身可能很糟糕,但我只是想在这里启发自己):

class A:
    def __init__(self, b):
        self.a = 123
        self.b = b
        print "a is {}".format(self.a)
    def __del__(self):
        self.a = -1
        print "a is {}".format(self.a)
        self.b.dont_gc_me = self
    def foo(self):
        self.a = 9999999
        print "a is {}".format(self.a)

class Foo:
    def __init__(self):
        self.a = 800
    def __del__(self):
        self.a = 0

然后尝试(在 IPython 控制台中)以下内容:

In [92]: f = Foo()

In [93]: a = A(f)
a is 123

In [94]: a = None
a is -1

In [95]: f.__dict__
Out[95]: {'a': 800, 'dont_gc_me': <__main__.A instance at 0x2456830>}

In [96]: f = None

In [97]: 

我看到 __del__ 只被调用一次,即使 A 的实例 a 已被 Foo 的实例 f 保持事件状态,当我将后者设置为 None 时,我没有看到第二次调用析构函数。

Python docs here 说:

Note that it is possible (though not recommended!) for the __del__() method to postpone destruction of the instance by creating a new reference to it. It may then be called at a later time when this new reference is deleted. It is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits.

这似乎意味着 __del__ 方法可能会被再次调用,尽管不能保证一定会。所以我的问题是:在某些情况下 __del__ 会被再次调用吗? (我认为将上面的 f 设置为 None 就可以了,但事实并非如此)。还有其他值得注意的细微差别吗?

最佳答案

这里有一个方法:

xs = []
n = 0

class A:
    def __del__(self):
        global n
        n += 1
        print("time {} calling __del__".format(n))
        xs.append(self)

print("creating an A immediately thrown away")
A()
for _ in range(5):
    print("popping from xs")
    xs.pop()

打印:

creating an A immediately thrown away
time 1 calling __del__
popping from xs
time 2 calling __del__
popping from xs
time 3 calling __del__
popping from xs
time 4 calling __del__
popping from xs
time 5 calling __del__
popping from xs
time 6 calling __del__

所以,简而言之,__del__ 的次数没有限制。可以调用。但不要依赖于此 - 语言最终可能会改变这里的“规则”。

循环使情况复杂化,因为当一个循环完全是垃圾时,没有可预测的顺序来销毁属于该循环的对象。因为它一个循环,每个对象都可以从循环中的每个其他对象访问,所以执行 __del__因为循环中的其中一个对象可能引用了一个已经被销毁的对象。那里很让人头疼,所以 CPython 只是拒绝收集一个循环,至少其中一个对象具有 __del__。方法。

但是,如果“挂起”垃圾循环的对象具有 __del__ 则没有问题。方法,前提是后一个对象本身不在垃圾循环中。示例:

class A:
    def __del__(self):
        print("A is going away")

class C:
    def __init__(self):
        self.self = self
        self.a = A()

然后:

>>> c = C()
>>> import gc
>>> gc.collect()  # nothing happens
0
>>> c = None  # now c is in a trash self-cycle
>>> gc.collect()  # c.a.__del__ *is* called
A is going away
2

所以这就是这个故事的寓意:如果您有一个“需要”运行析构函数但可能处于循环中的对象,请将 __del__在原始对象引用的简单对象中。像这样:

class CriticalResource:
    def __init__(self, resource):
        self.r = resource

    def __del__(self):
        self.r.close_nicely()  # whatever

class FancyObject:
    def __init__(self):
        # ...
        self.r = CriticalResource(get_some_resource())
        # ...

现在是 FancyObject可以有任意多个周期。当它变成垃圾时,循环不会阻止 CriticalResource__del__从被调用。

从 Python 3.4 开始

正如@delnan 在评论中指出的那样,PEP 442 changes the CPython rules从 Python 3.4 开始(但这不会向后移植到任何以前的 CPython 版本)。 __del__方法将最多执行一次(通过 gc - 当然用户可以显式调用它们任意次),并且对象是否具有 __del__ 将不再重要。方法是循环垃圾的一部分。

该实现将在循环垃圾中找到的所有对象上运行所有终结器,并在每个此类对象上设置一个位来记录其终结器已运行。它在任何对象被拆除之前执行此操作,因此没有终结器可以访问处于疯狂(部分或完全破坏)状态的对象。

该实现的缺点是终结器可以以任意方式更改对象图,因此如果循环垃圾中的任何内容再次可访问(“复活”),则当前循环垃圾收集(“gc”)运行必须放弃).这就是为什么终结器最多(将)被允许运行一次:否则 gc 可能会被一个在循环垃圾中复活死对象的终结器激怒而永远不会取得进展。

实际上,从 3.4 开始,CPython 对 __del__ 的处理方法将很像 Java 对终结器的处理。

关于python - 在 Python 中,每个对象可以调用 `__del__` 多少次?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18971451/

相关文章:

ios - 调用 ReleaseDesignerOutlets 对 MonoTouch GC 有什么影响吗?

c++ - 为什么在此代码中忽略了析构函数?

python - 如何更改 django 中的 URI?

python - Pandas 分组和组合行

python - 通过类的 __del__ 方法关闭 python 2.7.x 中的 sqlite3 连接是否可疑?

java - 字符串文字的存储长度会对性能产生影响吗?

python - 我在循环中使用了append(),由于某种原因,列表每次都会发生变化,而不是以应有的方式变化

Java - 在运行时检测内存交换

c++ - 在分配为不同类型的数组上使用 delete[] 是否安全?

c++ - 赋值运算符改变赋值对象的值