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

7

15

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.

Underrate answered 28/2, 2020 at 9:24 Comment(4)
__del__ made more sense back in the early days, when Python was purely reference-counted.Shrike
It also makes some sense to do cleanups in long running processes, like servers.Trella
__del__ is guaranteed to be called when the instance is about to be destroyed. What you've quoted is that the destruction is not guaranteed to happen when the interpreter exits. Which is a different story and I don't think that "try/finally" or "with" is helpful in this case. And so __del__ can be useful, e.g. automatic resource reclamation when programmers make mistakes and forget about "try/finally" or "with".Moore
Here's an example, though it might be repeatable by other means, and as you say, in complex code it might not get called.Toxinantitoxin
A
4

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.

Angelo answered 8/9, 2020 at 16:31 Comment(1)
Thanks, I was starting to come to the same conclusions. I feel like it's a pity though, the standard might just as well have said that when a program exits, the __del__() methods of all remaining objects must be called first. That way we could rely on it like in C++ and there would be no (or less) need for all the with clauses. It seems simple enough to implement, but perhaps I'm missing some implementation aspects that make it complicated.Underrate
S
4

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:

Straightjacket answered 31/8, 2020 at 9:6 Comment(0)
A
4

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.

Angelo answered 8/9, 2020 at 16:31 Comment(1)
Thanks, I was starting to come to the same conclusions. I feel like it's a pity though, the standard might just as well have said that when a program exits, the __del__() methods of all remaining objects must be called first. That way we could rely on it like in C++ and there would be no (or less) need for all the with clauses. It seems simple enough to implement, but perhaps I'm missing some implementation aspects that make it complicated.Underrate
G
1

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
Gaitan answered 31/8, 2020 at 9:32 Comment(1)
I don't understand why John would be printed a second time? Also, the 2nd print of Object closed is not guaranteed I think, since the standard says "It is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits." I did notice this works in some Python implementations but I don't think you can count on it.Underrate
C
1

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.

Crelin answered 1/9, 2020 at 12:14 Comment(7)
You say "you can rely on __del__ being called when a reference to an object is destroyed", but isn't that only true for reference-counting Python implementations? As far as I understood, an implementation is free to let the unreferenced object linger around for a while and then garbage-collect it at some point (calling __del__) or just leaving it until the program exits (without calling __del__)?Underrate
See object.__del__(self). Not in my reading of the CPython implementation. Specificlly: "CPython implementation detail: It is possible for a reference cycle to prevent the reference count of an object from going to zero. In this case, the cycle will be later detected and deleted by the cyclic garbage collector" This is a reference-counted implementation.Crelin
The point is that it only applies to the CPython implementation, not the Python language. @Pieter is right in saying that it is completely implementation-dependent when objects are garbage-collected, or, in fact, if they are even garbage-collected at all: "An implementation is allowed to postpone garbage collection or omit it altogether ...". That being said, at a practical level, I believe most popular Python implementations do garbage-collect objects when their reference count reaches 0, at least outside of program termination.Angelo
@Angelo I definitely should have clarified that my response about one being able to rely on __del__ being called when there were no more references pertained to CPython. But coding __del__ gives the class an additional chance at cleaning up if the client neglects to call close or cleanup or whatever you have chosen to call the cleanup routine. This was the major point of my answer and as we say, "better late than never" even if in a given implementation garbage collection is postponed (and you were no worse off than before if garbage collection is omitted all together).Crelin
@Crelin Yes, I've also decided to go for that interpretation; I think that's the best summary of the matter.Angelo
@Crelin I think the comment # A(2) and A(3) should now be destructed isn't correct. I don't see why A(2) should be destroyed at this point, since it's still referenced by a2. Your demo does say it gets destroyed, but that's probably just at the end of the program (where it also says a4 gets destroyed). If I'm right, the text below it is also wrong. Just wanted to see if you agree before editing your answer.Underrate
@Underrate You are, of course, correct -- this was a slip of the brain. I have updated the code myself and added some input statements at critical places to demonstrate that the objects have been destructed when expected. Thanks for pointing this out.Crelin
A
0

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

Antoine answered 7/9, 2020 at 6:27 Comment(2)
"A reference to objects is also deleted when the object goes out of reference" – what do you mean? I think you meant of scope (i.e. function closure).Angelo
Well, here reference means the location to which the object's name is pointing to. Example, in shivam = person(), shivam is a refernce pointing to an instance of person class in the memory, i.e., person()Antoine
B
0

I recently found an example where implementing __del__ was desired:

as mentioned in other answers, there is no guaranty as to where or even if __del__ will be called at all during the execution of your program. However, if you flip this statement around, this also means that there is a risk that it will be called at some point.

In my case I have a class that relies on a temporary file created during initialization, e.g.:

import tempfile

class A:
    def __init__(self):
        self.temp_file = tempfile.NamedTemporaryFile()

However, I also happen to rely on an external program that may delete that file (e.g. if its content is invalid). In this case, if this temporary file is garbage collected along with my instance of A, an exception will be raised because the file does not exist anymore.

So the solution is to try to close the file in the __del__ method and catch the exception there, if any:

    def __del__(self):
        try:
            self.temp_file.close()
        except FileNotFoundError:
            # Do something here
            pass

Hence by implementing __del__ in this case I can specify how the resources should be disposed of and what should be done if an exception was raised.

Bigelow answered 23/6, 2022 at 7:22 Comment(2)
But if you don't implement __del__(), it would do nothing, and no exception would be thrown if the file didn't exist, right? So how is this solution better than not implementing __del__()? Of course by implementing it, you increase the chance of the file being properly closed at some point, but it's not guaranteed.Underrate
If you don't implement it you may get an exception if the garbage collector decides to delete your instance, e.g. if you run the following code you get FileNotFoundError: pastebin.com/32F233w3. You need __del__ to catch this exception and specify how to properly get rid of this instance. Although in this case the exception is ignored so you might argue it's not such a big deal.Bigelow
C
-1

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

Crybaby answered 6/9, 2020 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.