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"?

+3


source to share


2 answers


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 in gc.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.

+6


source


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.

+2


source







All Articles