Python destructor called in the "wrong" order based on reference count

So, since the objects are still alive when the interpreter shuts down, you are actually not even guaranteed that __del__ will be called. At this point, the language makes no guarantees about when the finalizer is called.

From the docs:

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

Note, if you change the script to:

(py38) 173-11-109-137-SFBA:~ juan$ cat test.py
class A:
    def __init__(self, b):
        self.__b = b
        print("Construct A")

    def __del__(self):
        # It seems that the destructor of B is called here.
        print("Delete A")
        # But it should be called here

class B:
    def __init__(self):
        print("Construct B")

    def __del__(self):
        print("Delete B")
b = B()
a = A(b)

del a
del b

Then, executed:

(py38) 173-11-109-137-SFBA:~ juan$ python test.py
Construct B
Construct A
Delete A
Delete B

Although del does not delete objects, it deletes references, so it forces the reference count to reach 0 while the interpreter is still running, so the order is as you would expect.

Sometimes, __del__ won't be called at all. A common circumstance is file-objects created by

f = open('test.txt')

That have live references in the global scope. If not closed explicitly, it might not call __del__ and the file will not flush and you won't get anything written. Which is a great reason to use the file object as a context-manager...


Per the comments elsewhere on this question, you probably don't want to use __del__; it's not really a destructor in the C++ sense. You probably want to make the objects into context managers (by writing __enter__ and __exit__ methods) and use them in the with statement, and/or give them close methods which need to be called explicitly.

However, to answer the question as given: the reason is that both objects have references from the global variables a and b; neither reference count ever goes to zero. The destructor is called at the end when the python interpreter is shutting down and all the non-zero-count objects are being collected.

To see the behaviour you expect, put the a and b variables in a function so that the reference counts go to zero during the main part of execution.

class A:
    def __init__(self, b):
        self.__b = b
        print("Construct A")

    def __del__(self):
        print("Delete A")

class B:
    def __init__(self):
        print("Construct B")

    def __del__(self):
        print("Delete B")

def foo():
    b = B()
    a = A(b)

foo()