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

时间:2013-09-24 01:14:25

标签: 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保持活着,当我将后者设置为{{ 1}},我没有看到第二次调用析构函数。

Python docs here说:

  

请注意None可能(虽然不推荐!)   通过创建新的推迟销毁实例的方法   参考它。然后可以在稍后的时候调用它   参考被删除。不保证__del__()方法   调用解释器退出时仍然存在的对象。

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

3 个答案:

答案 0 :(得分:6)

这是一种方式:

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的{​​{1}}。

从Python 3.4开始

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

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

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

实际上,从3.4开始,CPython对__del__方法的处理将与Java对终结器的处理非常相似。

答案 1 :(得分:1)

永远不会为循环垃圾收集器收集的对象调用

__del__

因为在__del__的第一次调用中,您正在创建一个循环,所以您将依赖循环收集器来清理

再次调用__del__的唯一方法是手动中断循环引用

答案 2 :(得分:0)

Tim的答案是权威性的:终结者,__del__()或其他(例如,生成器函数中的finally子句),由于object resurrection,可以任意调用,这是实现 - 和版本相关。但是,从CPython 3.4开始,终结器仅按PEP 442 -- Safe object finalization调用一次,就像Java一样,感谢Antoine Pitrou

然而,这是乞求Pythonic的例子。这是一个:

import sys
cart = []
class PlagueVictim:
    RETORTS = ("I'm not dead.",
               "I'm getting better.",
               'I feel fine.',
               "I think I'll go for a walk.",
               'I feel happy. I feel happy.')
    DEFAULT_RETORT = "I'm still not dead."
    @classmethod
    def retort(cls, i):
        try: return cls.RETORTS[i]
        except IndexError: return cls.DEFAULT_RETORT
    def __init__(self): self.incarnation = 0
    def __del__(self):
        print(self.retort(self.incarnation))
        if cart is not None: cart.append(self)
        self.incarnation += 1

cart.append(PlagueVictim())
print("> Here's one.")
cart.pop() and None
if not cart: sys.exit()

print(">> 'ere, he says he's not dead.")
# Stubborn, aren't you?
cart.pop() and None
cart.pop() and None
cart.pop() and None
cart.pop() and None
del PlagueVictim.__del__
print('*thwack*')
cart.pop() and None
print('>> Ah, thank you very much.')
print(len(cart))

细心的读者会注意到None检查,这是因为在CPython&lt; 3.4(Issue18214)中关闭时模块全局变量设置为None,并在改进的最终化后修复,并且and None,确保引用未以交互模式存储在_(最后一个表达式的值)中。