Why do new style class and old style class have different behavior in this case?

后端 未结 1 1669
遇见更好的自我
遇见更好的自我 2021-01-11 14:53

I found something interesting, here is a snippet of code:

class A(object):
    def __init__(self):
        print \"A init\"

    def __del__(self):
        p         


        
相关标签:
1条回答
  • 2021-01-11 15:28

    TL;DR: this is an old issue in CPython, that was finally fixed in CPython 3.4. Objects kept live by reference cycles that are referred to by module globals are not properly finalized on interpreter exit in CPython versions prior to 3.4. New-style classes have implicit cycles in their type instances; old-style classes (of type classobj) do not have implicit reference cycles.

    Even though fixed in this case, the CPython 3.4 documentation still recommends to not depend on __del__ being called on interpreter exit - consider yourself warned.


    New style classes have reference cycles in themselves: most notably

    >>> class A(object):
    ...     pass
    >>> A.__mro__[0] is A
    True
    

    This means that they cannot be deleted instantly*, but only when the garbage collector is run. Since a reference to them is being held by the main module, they will stay in memory until the interpreter shutdown. At the end, during the module clean-up, all the module global names in the main are set to point to None, and whichever objects had their reference counts decreased to zero (your old-style class for example) were also deleted. However, the new-style classes, having reference cycles, would not be released/finalized by this.

    The cyclic garbage collector would not be run at the interpreter exit (which is allowed by the CPython documentation:

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


    Now, old-style classes in Python 2 do not have implicit cycles. When the CPython module cleanup/shutdown code sets the global variables to None, the only remaining reference to class B is dropped; then B is deleted, and the last reference to a is dropped, and a too is finalized.


    To demonstrate the fact that the new-style classes have cycles and require a GC sweep, whereas the old-style classes do not, you can try the following program in CPython 2 (CPython 3 does not have old-style classes any more):

    import gc
    class A(object):
        def __init__(self):
            print("A init")
    
        def __del__(self):
            print("A del")
    
    class B(object):
        a = A()
    
    del B
    print("About to execute gc.collect()")
    gc.collect()
    

    With B as new-style class as above, the output is

    A init
    About to execute gc.collect()
    A del
    

    With B as old-style class (class B:), the output is

    A init
    A del
    About to execute gc.collect()
    

    That is, the new-style class was deleted only after gc.collect() even though the last outside reference to it was dropped already; but the old-style class was deleted instantly.


    Much of this is already fixed in Python 3.4: thanks to PEP 442, which included the module shutdown procedure based on GC code. Now even on interpreter exit the module globals are finalized using the ordinary garbage collection. If you run your program under Python 3.4, the program will print

    A init
    A del
    

    Whereas with Python <=3.3 it will print

    A init
    

    (Do note that other implementations still might or might not execute __del__ at this moment, regardless of the version of them being above, at, or below, 3.4)

    0 讨论(0)
提交回复
热议问题