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.
__del__
made more sense back in the early days, when Python was purely reference-counted. – Shrike__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