Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How many times can `__del__` be called per object in Python?

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?

like image 545
scorpiodawg Avatar asked Sep 24 '13 01:09

scorpiodawg


People also ask

What does __ del __ do in Python?

__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.

How many objects can be created in Python?

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.

How do you call a class destructor in python?

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.


1 Answers

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.

Starting in Python 3.4

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.

like image 188
Tim Peters Avatar answered Sep 18 '22 09:09

Tim Peters