'What's the use of the __del__() method in Python?

From Python documentation:

It is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits.

As far as I understand, there is also no way to guarantee an object stops existing before the interpreter exits, since it's up to the garbage collector to decide if and when an object is deleted.

So what's the point of having this method at all? You can write cleanup code inside it, but there's no guarantee it will ever be executed.

I know you can solve this using try-finally or with clauses, but I still wonder what would be a meaningful use case of the __del__() method.



Solution 1:[1]

After reading all of these answers—none of which satisfactorily answered all of my questions/doubts—and rereading Python documentation, I've come to a conclusion of my own. This the summary of my thoughts on the matter.


Implementation-agnostic

The passage you quoted from the __del__ method documentation says:

It is not guaranteed that the __del__() methods are called for objects that still exist when the interpreter exits.

But not only is it not guaranteed that __del__() is called for objects being destroyed during interpreter exit, it is not even guaranteed that objects are garbage collected at all, even during normal execution—from the "Data model" section of the Python Language Reference:

Objects are never explicitly destroyed; however, when they become unreachable they may be garbage-collected. An implementation is allowed to postpone garbage collection or omit it altogether — it is a matter of implementation quality how garbage collection is implemented, as long as no objects are collected that are still reachable.

Thus, replying to your question:

So what's the point of having this method at all? You can write cleanup code inside it, but there's no guarantee it will ever be executed.

From an implementation-agnostic perspective, are there any uses for the __del__ method, as a fundamental component of one's code that can be relied on? No. None at all. It is essentially useless from this perspective.

From a practical point of view, though, as other answers have pointed out, you can use __del__ as a last-resort mechanism to (try to) ensure that any necessary cleanup is performed before the object is destroyed, e.g. releasing resources, if the user forgot to explicitly call a close method. This is not so much a fail-safe as it is a "it doesn't hurt to add an extra safety mechanism even if it's not guaranteed to work"—and in fact, most Python implementations will catch that most of the time. But it's nothing to be relied on.


Implementation-specific

That being said, if you know that your program will run on a specific set of Python implementations, then you can rely on the implementation details of garbage collection—for instance, if you use CPython, you can "rely on" the fact that, during normal execution (i.e. outside of interpreter exit), if the reference count of a non-cyclically-referenced object reaches zero, it will be garbage collected and its __del__ method will be called, as other answers have pointed out. From the same subsection as above:

CPython implementation detail: CPython currently uses a reference-counting scheme with (optional) delayed detection of cyclically linked garbage, which collects most objects as soon as they become unreachable, but is not guaranteed to collect garbage containing circular references.

But still, this is really precarious and something to not be really relied on, since as mentioned it is only guaranteed for objects that are not part of a cyclic reference graph. Also:

Other implementations act differently and CPython may change. Do not depend on immediate finalization of objects when they become unreachable (so you should always close files explicitly).


Bottom line

From a purist point of view, the __del__ method is completely useless. From a slightly less purist point of view, it is still almost useless. From a practical point of view, it might be useful as a complementary—but never essential—feature of your code.

Solution 2:[2]

It can be used to dispose of resources managed by the object : https://github.com/python/cpython/blob/master/Lib/zipfile.py#L1805

As noted in the docstring, this is a kind of last resort as the object with be closed only when gc is running.

As you said in your question, the prefered way is to call close yourself, either by calling .close() directly or using a context manager with Zipfile() as z:

Solution 3:[3]

It's basically used to forcefully call a method which should be called once all activity by that object is finished, Like

def __del__(self):
    self.my_func()

And now you are sure that my_func will be called everything the object's job are done.

Run this program and you get what's going on

class Employee: 
  
    def __init__(self, name): 
        self.name = name
        print('Employee created.') 
    
    def get_name(self):
        return self.name

    def close(self):
        print("Object closed")

    # destructor
    def __del__(self):
        self.close()
  
obj = Employee('John')

print(obj.get_name())

# lets try deleting the object!
obj.__del__() # you don't need to run this

print("Program ends")

print(obj.get_name())

Output

> Employee created.
> John 
> Object closed 
> Program ends  
> John 
> Object closed

Solution 4:[4]

You said:

It is not guaranteed that del() methods are called for objects that still exist when the interpreter exits.

That is very true but there are many cases where objects are created and then references to those objects are either explicitly "destroyed" by being set to None or go out of scope. These objects, either when created or during the course of execution, allocate resources. When the user is through with the object he should be calling a close or cleanup method to free up those resources. But it would be a good practice to have a destructor method, i.e. __del__ method, that is called when there are no more references to the object that can check whether that cleanup method has been called and if not calls the cleanup itself. In the case of __del__ possibly not being called at exit, it may not be too important at this point to reclaim resources since the program is being terminated anyway (of course, in the case of cleanup or close doing more than just reclaiming resources but also performing a necessary terminating function such as closing a file, then relying on __del__ being called at exit does become problematic).

The point is that, in reference-counting implementations such as CPython, you can rely on __del__ being called when the last reference to an object is destroyed:

import sys

class A:
    def __init__(self, x):
        self.x = x

    def __del__(self):
        print(f'A x={self.x} being destructed.')


a1 = A(1)
a2 = A(2)
a1 = None
# a1 is now destroyed
input('A(1) should have been destroyed by now ...')
a_list = [a2]
a_list.append(A(3))
a_list = None # A(3) should now be destroyed
input('A(3) should have been destroyed by now ...')
a4 = A(4)
sys.exit(0) # a2 and a4 may or may not be garbage collected

Prints:

A x=1 being destructed.
A(1) should have been destroyed by now ...
A x=3 being destructed.
A(3) should have been destroyed by now ...
A x=2 being destructed.
A x=4 being destructed.

With the possible exception of objects a2 and a4, all the other instances of class A will be "destructed", i.e. destroyed.

The actual usage is, for example, where a function bar is called that creates an instance of a B which creates an instance of an A. When the function bar returns the reference to B and thus to A is implicitly destroyed and cleanup will automatically be done if the close method on the A instance has not be called:

class A:
    def __init__(self, x):
        self.x = x
        self.cleanup_done = False
 
    def close(self):
        print(f'A x={self.x} being cleaned up.')
        self.cleanup_done = True
 
 
    def __del__(self):
        if not self.cleanup_done:
            self.close()
 
 
class B:
    def __init__(self, x):
        self.a = A(x)
 
    def foo(self):
        print("I am doing some work")
 
 
def bar():
    b = B(9)
    b.foo()
 
def other_function():
    pass
 
if __name__ == '__main__':
    bar()
    other_function()

Python Demo

For the B instance to explicitly call the A instance's close method, it would have to implement its own close method which it then delegates to the A instance's close method. There is no point, however, in it using a __del__ method for that purpose. For if that were to work, then the A instance's own __del__ method would be sufficient for cleanup.

Solution 5:[5]

Destructors are called when an object gets destroyed. In Python, destructors are not needed as much needed in C++ because Python has a garbage collector that handles memory management automatically.

The __del__() method is a known as a destructor method in Python. It is called when all references to the object have been deleted i.e when an object is garbage collected.

Syntax of destructor declaration :

def __del__(self):
  # body of destructor

Note : A reference to objects is also deleted when the object goes out of reference or when the program ends.

Example 1 : Here is the simple example of destructor. By using del keyword we deleted the all references of object ‘obj’, therefore destructor invoked automatically.

    # Python program to illustrate destructor 
    class Employee: 
      
        # Initializing 
        def __init__(self): 
            print('Employee created.') 
      
        # Deleting (Calling destructor) 
        def __del__(self): 
            print('Destructor called, Employee deleted.') 
      
    obj = Employee() 
    del obj 

#Output
#Employee created.
#Destructor called, Employee deleted.

Note : The destructor was called after the program ended or when all the references to object are deleted i.e when the reference count becomes zero, not when object went out of scope.

Example 2 :This example gives the explanation of above mentioned note. Here, notice that the destructor is called after the ‘Program End…’ printed.

# Python program to illustrate destructor 
  
class Employee: 
  
    # Initializing  
    def __init__(self): 
        print('Employee created') 
  
    # Calling destructor 
    def __del__(self): 
        print("Destructor called") 
  
def Create_obj(): 
    print('Making Object...') 
    obj = Employee() 
    print('function end...') 
    return obj 
  
print('Calling Create_obj() function...') 
obj = Create_obj() 
print('Program End...') 

#Output:
#Calling Create_obj() function...
#Making Object...

Example 3 : Now, consider the following example :

# Python program to illustrate destructor 
  
class A: 
    def __init__(self, bb): 
        self.b = bb 
  
class B: 
    def __init__(self): 
        self.a = A(self) 
    def __del__(self): 
        print("die") 
  
def fun(): 
    b = B() 
  
fun() 

#Output:
#die

In this example when the function fun() is called, it creates an instance of class B which passes itself to class A, which then sets a reference to class B and resulting in a circular reference.

Generally, Python’s garbage collector which is used to detect these types of cyclic references would remove it but in this example the use of custom destructor marks this item as “uncollectable”. Simply, it doesn’t know the order in which to destroy the objects, so it leaves them. Therefore, if your instances are involved in circular references they will live in memory for as long as the application run.

Source: Destructors in Python

Solution 6:[6]

__del()__ work like destructor, and sometimes it is called automatically through garbage collector (and i said sometimes not always because you can never know if it will run and when),so it is kinda useless than useful to use.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Cyril Jouve
Solution 3 Farhad Hossain
Solution 4 PieterNuyts
Solution 5 Shivam Jha
Solution 6 ghazigamer