我看到了一些代码,其中__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}},我没有看到第二次调用析构函数。
请注意
None
可能(虽然不推荐!) 通过创建新的推迟销毁实例的方法 参考它。然后可以在稍后的时候调用它 参考被删除。不保证__del__()
方法 调用解释器退出时仍然存在的对象。
这似乎意味着可以再次调用__del__()
方法,但不能保证它会。所以我的问题是:是否有一些情况会再次调用__del__
? (我认为将__del__
设置为f
就可以了,但事实并非如此。还有其他值得注意的细微差别吗?
答案 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}}。
正如@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)
__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
,确保引用未以交互模式存储在_
(最后一个表达式的值)中。