¿Por qué la clase de estilo nuevo y la clase de estilo antiguo tienen un comportamiento diferente en este caso?

Encontré algo interesante, aquí hay un fragmento de código:

class A(object): def __init__(self): print "A init" def __del__(self): print "A del" class B(object): a = A() 

Si ejecuto este código, obtendré:

 A init 

Pero si cambio la class B(object) a la class B() , obtendré:

 A init A del 

Encontré una nota en el __del__ doc :

No se garantiza que los métodos del () se llamen para objetos que aún existen cuando el intérprete sale.

Entonces, supongo que es porque todavía se hace referencia a Ba (referenciada por la clase B ) cuando existe el intérprete.

Entonces, agregué un del B antes de que el intérprete exista manualmente, y luego encontré que se llamó a.__del__() .

Ahora, estoy un poco confundido acerca de eso. ¿Por qué se llama a.__del__() cuando se usa una clase de estilo antiguo? ¿Por qué las clases de estilo antiguo y nuevo tienen un comportamiento diferente?

Encontré una pregunta similar aquí , pero creo que las respuestas no son lo suficientemente claras.

TL; DR: este es un problema antiguo en CPython, que finalmente se solucionó en CPython 3.4 . Los objetos mantenidos en vivo por ciclos de referencia a los que hacen referencia los módulos globales no se finalizan correctamente en la salida del intérprete en las versiones CPython anteriores a 3.4. Las clases de nuevo estilo tienen ciclos implícitos en sus instancias de type ; Las clases de estilo antiguo (de tipo classobj ) no tienen ciclos de referencia implícitos.

Aunque se corrigió en este caso, la documentación de CPython 3.4 todavía recomienda no depender de que se __del__ a __del__ a la salida del intérprete; considérese advertido.


Las nuevas clases de estilo tienen ciclos de referencia en sí mismos: más notablemente

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

Esto significa que no se pueden eliminar al instante *, pero solo cuando se ejecuta el recolector de basura. Como el módulo principal retiene una referencia a ellos, permanecerán en la memoria hasta que se apague el intérprete. Al final, durante la limpieza del módulo, todos los nombres globales del módulo en el principal se configuran para señalar a None , y los objetos a los que se redujo a cero su cuenta de referencia (su clase de estilo antiguo, por ejemplo) también se eliminaron. Sin embargo, las clases de nuevo estilo, que tienen ciclos de referencia, no serían publicadas / finalizadas por esto.

El recolector de basura cíclico no se ejecutaría en la salida del intérprete (que está permitido por la documentación de CPython) :

No se garantiza que se __del__() métodos __del__() para los objetos que aún existen cuando el intérprete sale.


Ahora, las clases de estilo antiguo en Python 2 no tienen ciclos implícitos. Cuando el código de limpieza / apagado del módulo CPython establece las variables globales en None , se None la única referencia restante a la clase B ; luego se elimina B , y se elimina la última referencia a a , y también se finaliza a.


Para demostrar el hecho de que las clases de nuevo estilo tienen ciclos y requieren un barrido de GC, mientras que las clases de estilo antiguo no lo hacen, puede probar el siguiente progtwig en CPython 2 (CPython 3 ya no tiene clases de estilo antiguo):

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

Con B como clase de nuevo estilo como arriba, la salida es

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

Con B como clase de estilo antiguo ( class B: , la salida es

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

Es decir, la clase de nuevo estilo se eliminó solo después de gc.collect() aunque la última referencia externa ya se eliminó; pero la clase de estilo antiguo se eliminó al instante.


Gran parte de esto ya está solucionado en Python 3.4 : gracias a PEP 442 , que incluía el procedimiento de apagado del módulo basado en el código GC . Ahora, incluso al salir del intérprete, los módulos globales se finalizan utilizando la recolección de basura ordinaria. Si ejecuta su progtwig bajo Python 3.4, el progtwig se imprimirá

 A init A del 

Mientras que con Python <= 3.3 se imprimirá

 A init 

( Tenga en cuenta que otras implementaciones aún podrían o no ejecutar __del__ en este momento, independientemente de que la versión de las mismas esté por encima, en o por debajo de 3.4)