Why can't I get the expected "BBB" result?
# -*- coding: utf-8 -*-
class tA():
def __init__(self):
print 'AAA'
def __del__(self):
print 'BBB'
class tC(tA):
def __init__(self, a, b=0):
tA.__init__(self)
self.b=b # 1. del this ok
class tD(tC):
def __init__(self):
a=1
#tC.__init__(self, a) # 2. use this ok
tC.__init__(self, a, self.func) # 3. use this not ok
#tC.__init__(self, a, 3) # 4. use this ok
def func(self, pos):
pass
if __name__ == '__main__':
tD()
why is there no "BBB" output?
if i del zhe # 1 the output is ok
if i use # 2 or # 4 the output is ok
if i use # 3, the output has no "BBB"?
source to share
Since yours 'BBB'
is printed with your class finalizer (function __del__
). And finalizers run when the garbage collector collects your object.
Python uses a double strategy of garbage collection: reference counting and loop detection. Objects whose reference count reaches 0 are collected immediately, but if they are in a loop, their count will never reach 0. Then, the GC loop detection routine, which is called periodically, will eventually find it and release any dangling objects.
In your specific code, case # 3 creates a reference loop: self.b
is a reference to self.func
. But GC loop detection never starts because the program ends before it has a chance.
But even if the GC is running, objects with finalizers have special rules. From the doc:
Objects that have methods
__del__()
and are part of the reference loop cause the entire reference loop to be useless , including objects, not necessarily in the loop, but only accessible from within. Python does not collect such loops automatically because, in general, Python cannot guess the safe order in which methods are run__del__()
.
Also, from here
Changed in version 3.4: After PEP 442, method objects are
__del__()
no longer included ingc.garbage
.
So it looks like in Python before 3.4, in classes with finalizers, you have to manually break the loops:
If you know the safe ordering, you can force fix the problem by looking at the garbage list and explicitly breaking the loops because of your objects in the list. Note that these objects are kept alive even if they are in the garbage list, so they should be removed from the garbage as well. For example, after breaking the loops, do
del gc.garbage[:]
to delete the list.
source to share
Because it func
is a bound method and indirectly refers to the object to which it is bound, which means that you are creating a reference loop.
You can check this by running:
...
if __name__ == '__main__':
import sys
print(sys.getrefcount(tD()))
which should print 1
in your cases # 2 and # 4 and 2
in case # 3.
The documentation __del__
has a note about reference loops:
[...] Some common situations that can prevent an object's reference count from going to zero include: circular references between objects (for example, a doubly linked list or tree data structure with parent and child pointers); a reference to an object on the stack stack of the function that caught the exception (the trace stored in sys.exc_traceback maintains a stack frame); or a reference to an object in the stack frame that raised an unhandled exception interactively (the trace stored in sys.last_traceback keeps the stack frame alive)
[...]
Circular references that are garbage are detected when option loop detectors are enabled (by enabled by default), but can only be cleared if no methods are involved__del__()
at the Python level.
This basically means that if you have a method __del__
, it prevents the objects containing the reference loop from being cleaned up.
source to share