What Is the _Del_ Method and How to Call It

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

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.

I don't understand this python __del__ behaviour

I'm providing my own answer because, while I appreciate the advice to avoid __del__, my question was how to get it to work properly for the code sample provided.

Short version: The following code uses weakref to avoid the circular reference. I thought I'd tried this before posting the question, but I guess I must have done something wrong.

import types, weakref

class Dummy():
def __init__(self, name):
self.name = name
def __del__(self):
print "delete",self.name

d2 = Dummy("d2")
def func(self):
print "func called"
d2.func = types.MethodType(func, weakref.ref(d2)) #This works
#d2.func = func.__get__(weakref.ref(d2), Dummy) #This works too
d2.func()
del d2
d2 = None
print "after d2"

Longer version:
When I posted the question, I did search for similar questions. I know you can use with instead, and that the prevailing sentiment is that __del__ is BAD.

Using with makes sense, but only in certain situations. Opening a file, reading it, and closing it is a good example where with is a perfectly good solution. You've gone a specific block of code where the object is needed, and you want to clean up the object and the end of the block.

A database connection seems to be used often as an example that doesn't work well using with, since you usually need to leave the section of code that creates the connection and have the connection closed in a more event-driven (rather than sequential) timeframe.

If with is not the right solution, I see two alternatives:

  1. You make sure __del__ works (see this blog for a better
    description of weakref usage)
  2. You use the atexit module to run a callback when your program closes. See this topic for example.

While I tried to provide simplified code, my real problem is more event-driven, so with is not an appropriate solution (with is fine for the simplified code). I also wanted to avoid atexit, as my program can be long-running, and I want to be able to perform the cleanup as soon as possible.

So, in this specific case, I find it to be the best solution to use weakref and prevent circular references that would prevent __del__ from working.

This may be an exception to the rule, but there are use-cases where using weakref and __del__ is the right implementation, IMHO.

__del__ method being called in python when it is not expected

There are a couple of things going on here. When your Person4 class is instantiated, it initialises its population class variable to 0. From your interactive console, you appear to be running your "test1.py" file multiple times. The second time you run it, the Person4 class is declared again which makes it technically different from the first one (even though it has the same name). That means it has its own independent population count.

Now, swaroop and kaleem are global variables, shared between both your instances of "test1.py". Python internally uses reference counting for most of its automatic garbage collection, so the original instance of the first Person4 class is not released until the second assignment to swaroop. Assigning to swaroop decrements the reference count for the first instance, causing __del__ to be called because the reference count is now zero. But because you're referring to Person4 by name inside __del__(), when the previous instance disappears it decrements the new Person4.population count, instead of the old Person4 population count.

Hopefully that made sense. I can see why this might be confusing to somebody learning Python. Your use of class variables at the same time as redefining the Person4 class using execfile() is further confusing matters. For what it's worth, I've written a lot of Python code and I don't think I've ever needed to use the __del__ special method.

Is __del__ really a destructor?

In the Python 3 docs the developers have now made clear that destructor is in fact not the appropriate name for the method __del__.

object.__del__(self)

Called when the instance is about to be destroyed. This is also called a finalizer or (improperly) a destructor.

Note that the OLD Python 3 docs used to suggest that 'destructor' was the proper name:

object.__del__(self)

Called when the instance is about to be destroyed. This is also called a destructor. If a base class has a __del__() method, the derived class’s __del__() method, if any, must explicitly call it to ensure proper deletion of the base class part of the instance.

From other answers but also from the Wikipedia:

In a language with an automatic garbage collection mechanism, it would be difficult to deterministically ensure the invocation of a destructor, and hence these languages are generally considered unsuitable for RAII [Resource Acquisition Is Initialization]

So you should almost never be implementing __del__, but it gives you the opportunity to do so in some (rare?) use cases

How to ensure the __del__ function is called on a Python class as is commonly (but incorrectly) expected?

If you understand all that, why not do it in the Pythonic way? Compare another class where cleanup is important: tempfile.TemporaryDirectory.

with TemporaryDirectory() as tmp:
# ...
# tmp is deleted

def foo():
tmp = TemporaryDirectory()
foo()
# tmp is deleted

How do they do this? Here's the relevant bit:

import weakref
class Foo():
def __init__(self, name):
self.name = name
self._finalizer = weakref.finalize(self, self._cleanup, self.name)
print("%s reporting for duty!" % name)

@classmethod
def _cleanup(cls, name):
print("%s feels forgotten! Bye!" % name)

def cleanup(self):
if self._finalizer.detach():
print("%s told to go away! Bye!" % self.name)

def foo():
print("Calling Arnold")
tmpfoo = Foo("Arnold")
print("Finishing with Arnold")

foo()
# => Calling Arnold
# => Arnold reporting for duty
# => Finishing with Arnold
# => Arnold feels forgotten. Bye!

def bar():
print("Calling Rocky")
tmpbar = Foo("Rocky")
tmpbar.cleanup()
print("Finishing with Rocky")

bar()
# => Calling Rocky
# => Rocky reporting for duty!
# => Rocky told to go away! Bye!
# => Finishing with Rocky

weakref.finalize will trigger _cleanup when the object is garbage-collected, or at the end of the program if it's still around. We can keep the finaliser around so that we can explicitly kill the object (using detach) and mark it as dead so the finaliser is not called (when we want to manually handle the cleanup).

If you want to support the context usage with with, it is trivial to add __enter__ and __exit__ methods, just invoke cleanup in __exit__ ("manual cleanup" as discussed above).

How does the python del function work without calling __getitem__

del is not a function. It can do this because it's not a function. This is why it's not a function. It's a keyword built into the language as part of the del statement.

To keep in mind that things like del and return aren't functions (and avoid unexpected precedence surprises), it's best to not put parentheses around the "argument":

del whatever

rather than

del(whatever)

del does not take an object and delete it. The thing to the right of del is not an expression to be evaluated. It is a target_list, the same kind of syntax that appears on the left side of the = in an assignment statement:

target_list     ::=  target ("," target)* [","]
target ::= identifier
| "(" [target_list] ")"
| "[" [target_list] "]"
| attributeref
| subscription
| slicing
| "*" target

To delete a subscription target like obj[k], Python evaluates the expression obj and the expression k to produce two objects, then calls the __delitem__ method of the first object with the second object as the argument. obj[k] is never evaluated as an expression, though pieces of it are.


This all relies on compiler and grammar support, and cannot be done for arbitrary user-defined functions.



Related Topics



Leave a reply



Submit