Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

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

    def __del__(self):
        print "A del"

class B(object):
    a = A()

If I run this code, I will get:

A init

But if I change class B(object) to class B(), I will get:

A init
A del

I found a note in the __del__ doc:

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

Then, I guess it's because that B.a is still referenced(referenced by class B) when the interpreter exists.

So, I added a del B before the interpreter exists manually, and then I found a.__del__() was called.

Now, I am a little confused about that. Why is a.__del__() called when using old style class? Why do new and old style classes have different behavior?

I found a similar question here, but I think the answers are not clear enough.

like image 542
WKPlus Avatar asked Apr 08 '15 09:04

WKPlus


People also ask

What is the difference between old style and new style classes in Python?

New-style classes inherit from object, or from another new-style class. Old-style classes don't.

Does Python 2 have classes?

Python classes provide all the standard features of Object Oriented Programming: the class inheritance mechanism allows multiple base classes, a derived class can override any methods of its base class or classes, and a method can call the method of a base class with the same name.


1 Answers

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)

like image 121