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()