5

我看到一些代码在__del__我发现好奇的对象上被显式调用,所以我尝试使用它来了解它是如何工作的。

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

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 ofA已被实例 f 保持活动状态,它也只调用一次Foo,并且当我将后者设置为 时None,我没有看到第二次调用析构函数。

这里的Python 文档说:

请注意,该方法可以(尽管不推荐!)__del__() 通过创建对实例的新引用来推迟实例的销毁。然后可以在稍后删除此新引用时调用它。不能保证__del__()为解释器退出时仍然存在的对象调用方法。

这似乎意味着该__del__方法可能会被再次调用,尽管不能保证它会被再次调用。所以我的问题是:在某些情况下__del__会再次调用吗?(我认为设置fNone上面会做到这一点,但它没有)。还有其他值得注意的细微差别吗?

4

3 回答 3

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__一个对象执行 a可能会引用一个已经被销毁的对象。那里很头疼,所以 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())
        # ...

现在 aFancyObject可以在任意多个周期中。当它变成垃圾时,循环不会阻止CriticalResource's__del__被调用。

从 Python 3.4 开始

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

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

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

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

于 2013-09-24T02:39:49.263 回答
1

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

由于在您的第一次通话__del__中创建了一个循环,您将依赖循环收集器来清理

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

于 2013-09-24T01:21:15.353 回答
0

Tim 的回答是权威的:终结器__del__()或其他方式(例如,finally生成器函数中的子句),由于对象复活,可以经常被任意调用,这取决于实现和版本。然而,从 CPython 3.4 开始,终结器只被调用一次,根据PEP 442——安全对象终结,就像 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))

细心的读者会注意到检查,这是因为模块全局变量在 CPython <3.4 ( Issue18214None ) 中被设置为关闭时,在改进的终结后修复,以及确保引用没有被存储在(最后一个表达式的值) 在交互模式下。Noneand None_

于 2014-04-24T13:45:47.390 回答