I saw some code where __del__
was being called explicitly on an object which I found curious, so I tried to play with it a bit to understand how it works.
I tried the following code (I understand using __del__
in and of itself is potentially bad, but I'm just trying to edify myself here):
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
and then tried (in the IPython console) the following:
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]:
I see that __del__
is only called once even though the instance a of A
has been kept alive by the instance f of Foo
, and when I set the latter to None
, I don't see the destructor being called the second time.
The Python docs here say:
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.
This seems to imply that the __del__
method may be called again, though there is no guarantee it will. So my question is: is there some circumstance where __del__
will be called again? (I thought setting f
to None
above would do it, but it didn't). Any other nuances worth noting?
__del__ is a destructor method which is called as soon as all references of the object are deleted i.e when an object is garbage collected. Example: Here is the simple example of destructor. By using del keyword we deleted the all references of object 'obj', therefore destructor invoked automatically.
Now, the user can create as many objects as he wants and can access data members, methods, and class variables from it. A user can define one or more classes and perform different actions using objects.
The magic method __del__() is used as the destructor in Python. The __del__() method will be implicitly invoked when all references to the object have been deleted, i.e., is when an object is eligible for the garbage collector. This method is automatically called by Python when the instance is about to be destroyed.
Here's a way:
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()
That prints:
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__
So, in short, there's no limit on how many times __del__
can be called. But don't rely on this - the language may change "the rules" here eventually.
Cycles complicate the situation because when a cycle is entirely trash, there's no predictable order in which the objects belonging to the cycle will get destroyed. Since it is a cycle, every object is reachable from every other object in the cycle, so executing a __del__
for one of the objects in the cycle may reference an object that's already been destroyed. Major headaches there, so CPython simply refuses to collect a cycle at least one of whose objects has a __del__
method.
However, it's no problem if an object "hanging off" a trash cycle has a __del__
method, provided the latter object is not itself in a trash cycle. Example:
class A:
def __del__(self):
print("A is going away")
class C:
def __init__(self):
self.self = self
self.a = A()
Then:
>>> 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
So that's the moral of this story: if you have an object that "needs" to run a destructor, but may be in a cycle, put the __del__
in a simple object referenced by the original object. Like so:
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())
# ...
Now a FancyObject
can be in as many cycles as you like. When it becomes trash, the cycles won't prevent CriticalResource
's __del__
from being invoked.
As @delnan noted in a comment, PEP 442 changes the CPython rules starting in Python 3.4 (but this will not be backported to any previous CPython release). __del__
methods will be executed at most once then (by gc - of course users can explicitly call them any number of times), and it will no longer matter whether an object with a __del__
method is part of cyclic trash.
The implementation will run all finalizers on all objects found in cyclic trash, and set a bit on each such object recording that its finalizer has been run. It does this before any objects are torn down, so no finalizer can access an object in an insane (partially or wholly destructed) state.
The downside for the implementation is that finalizers can change the object graph in arbitrary ways, so the current cyclic garbage collection ("gc") run has to give up if anything in the cyclic trash has become reachable again ("resurrected"). That's why finalizers are (will be) allowed to run at most once: else gc could be provoked into never making progress by a finalizer that resurrected dead objects in cyclic trash.
In effect, starting in 3.4, CPython's treatment of __del__
methods will be a lot like Java's treatment of finalizers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With