23

From the python docs:

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

Why not? What problems would occur if this guarantee were made?

3
  • In which order would you call the __del__() methods for objects in the same scope? Commented Jan 31, 2013 at 14:51
  • By the order in which those objects reference each other, or if there are cycles then you already have the "no __del__() in cycles" rule. Commented Jan 31, 2013 at 15:04
  • For Python-the-language, this is not guaranteed because in some implementations it is not possible to implement. E.g. the Java Python port is dependent on what the JVM garbage collector does and cannot just change that. But I am not sure if that documentation is supposed to describe the language or the CPython implementation. As strubbly answered, it seems to be guaranteed in the newer CPython versions, but wasn't in older ones, most likely due to the difficulty of implementing it correctly and predictably. Commented Dec 4, 2017 at 16:09

5 Answers 5

10

I'm not convinced by the previous answers here.

Firstly note that the example given does not prevent __del__ methods being called during exit. In fact, the current CPythons will call the __del__ method given, twice in the case of Python 2.7 and once in the case of Python 3.4. So this can't be the "killer example" which shows why the guarantee is not made.

I think the statement in the docs is not motivated by a design principle that calling the destructors would be bad. Not least because it seems that in CPython 3.4 and up they are always called as you would expect and this caveat seems to be moot.

Instead I think the statement simply reflects the fact that the CPython implementation has sometimes not called all destructors on exit (presumably for ease of implementation reasons).

The situation seems to be that CPython 3.4 and 3.5 do always call all destructors on interpreter exit.

CPython 2.7 by contrast does not always do this. Certainly __del__ methods are usually not called on objects which have cyclic references, because those objects cannot be deleted if they have a __del__ method. The garbage collector won't collect them. While the objects do disappear when the interpreter exits (of course) they are not finalized and so their __del__ methods are never called. This is no longer true in Python 3.4 after the implementation of PEP 442.

However, it seems that Python 2.7 also does not finalize objects that have cyclic references, even if they have no destructors, if they only become unreachable during the interpreter exit.

Presumably this behaviour is sufficiently particular and difficult to explain that it is best expressed simply by a generic disclaimer - as the docs do.

Here's an example:

class Foo(object): def __init__(self): print("Foo init running") def __del__(self): print("Destructor Foo") class Bar(object): def __init__(self): print("Bar1 init running") self.bar = self self.foo = Foo() b = Bar() # del b 

With the del b commented out, the destructor in Foo is not called in Python 2.7 though it is in Python 3.4.

With the del b added, then the destructor is called (at interpreter exit) in both cases.

Sign up to request clarification or add additional context in comments.

2 Comments

This answer is wrong. Python 3.4+ still makes no guarantee that destructors will be called. Here's a simple example based on sebix's answer down the page. Python 3.4+ tries a bit harder than earlier versions did, but it's still pretty much just best-effort basis.
@user2357112 your example runs __del__ in python 3.7 (not sure if it's actually guaranteed though).
5

If you did some nasty things, you could find yourself with an undeletable object which python would try to delete forever:

class Phoenix(object): def __del__(self): print "Deleting an Oops" global a a = self a = Phoenix() 

Relying on __del__ isn't great in any event as python doesn't guarantee when an object will be deleted (especially objects with cyclic references). That said, perhaps turning your class into a context manager is a better solution ... Then you can guarantee that cleanup code is called even in the case of an exception, etc...

7 Comments

Hm... I agree with your second example, but your first example can be avoided by seeing that a holds a reference to lst so a should be deleted first, and the RuntimeError issue doesn't occur because the docs explicitly say exceptions during destructors behave differently. But the second example is ridiculous, why limit things for that stupid use case?
@SheaLevy -- The point is that python provides an excellent facility for automatically handling cleanup (look into context managers). It's just a safer way of handling things.
But context managers don't cover all use cases. Sometimes you need a resource open across multiple threads and only want to cleanup after all of the threads are done, or sometimes you have a long-lived resource that's needed in multiple places that can't all be covered in a single scope.
Anyway, this seems the right answer from the perspective of "why it is this way", even if I'm not convinced it needs to be.
@SheaLevy I don't agree that this is right - and I've added my own answer. For a start, destructors are called on exit almost always, so it can't be fundamentally difficult - and the given example works fine. And Python 3 always works. It's just that Python 2 doesn't call the garbage collector again after deleting all the globally scoped names. And rather than explain that in detail the documenters added a generic exclusion.
|
1

I don't think this is because doing the deletions would cause problems. It's more that the Python philosophy is not to encourage developers to rely on the use of object deletion, because the timing of these deletions cannot be predicted - it is up to the garbage collector when it occurs.

If the garbage collector may defer deleting unused objects for an unknown amount of time after they go out of scope, then relying on side effects that happen during the object deletion is not a very robust or deterministic strategy. RAII is not the Python way. Instead Python code handles cleanup using context managers, decorators, and the like.

Worse, in complicated situations, such as with object cycles, the garbage collector might not ever detect that objects can be deleted. This situation has improved as Python has matured. But because of exceptions to the expected GC behaviour like this, it is unwise for Python developers to rely on object deletion.

I speculate that interpreter exit is another complicated situation where the Python devs, especially for older versions of Python, were not completely strict about making sure the GC delete ran on all objects.

Comments

1

One example where the destructor is not called is, if you exit inside a method. Have a look at this example:

class Foo(object): def __init__(self): print("Foo init running") def __del__(self): print("Destructor Foo") class Bar(object): def __init__(self): print("Bar1 init running") self.bar = self self.foo = Foo() def __del__(self): print("Destructor Bar") def stop(self): del self.foo del self sys.exit(1) b = Bar() b.stop() 

The output is:

Bar1 init running Foo init running Destructor Foo 

As we destruct foo explicitly, the destructor is called, but not the destructor of bar!

And, if we do not delete foo explicitly, it is also not destructed properly:

class Foo(object): def __init__(self): print("Foo init running") def __del__(self): print("Destructor Foo") class Bar(object): def __init__(self): print("Bar1 init running") self.bar = self self.foo = Foo() def __del__(self): print("Destructor Bar") def stop(self): sys.exit(1) b = Bar() b.stop() 

Output:

Bar1 init running Foo init running 

1 Comment

You don't even have to exit within a method. Calling exit at module level prevents the __del__ call too.
0

Likely because most of programmers would assume that destructors should only be called on dead (already unreachable) objects, and here on exit we would invoke them on live objects.

If it the developer has not been expecting a destructor call on the live object, some nasty UB may result. At least, something must be done to force-close the application after time out if it hangs. But then some destructors may not be called.

Java Runtime.runFinalizersOnExit has been deprecated because of the same reason.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.