Casos de uso para __del__

¿Cuáles son los casos de uso en python 3 al escribir un método __del__ personalizado o confiar en uno de stdlib 1 ? Es decir, ¿en qué escenario es razonablemente seguro y puede hacer algo que es difícil hacer sin él?

Por muchas buenas razones ( 1 2 3 4 5 6 ), la recomendación habitual es evitar __del__ y en su lugar usar administradores de contexto o realizar la limpieza manualmente:

  1. __del__ se garantiza que __del__ se llame si los objetos están vivos en la salida 2 del intrepreter.
  2. En el punto en que se espera que el objeto pueda ser destruido, el recuento de ref puede ser en realidad distinto de cero (por ejemplo, una referencia puede sobrevivir a través de una ttwig de rastreo retenida por una función de llamada). Esto hace que el tiempo de destrucción sea mucho más incierto que lo que implica la mera impredecibilidad de gc .
  3. El recolector de basura no puede deshacerse de los ciclos si incluyen más de 1 objeto con __del__
  4. El código dentro de __del__ debe escribirse súper cuidadosamente:
    • los atributos de objeto establecidos en __init__ pueden no estar presentes ya que __init__ podría haber generado una excepción;
    • las excepciones se ignoran (solo se imprimen a stderr );
    • Es posible que los globales ya no estén disponibles.

Actualizar:

PEP 442 ha hecho mejoras significativas en el comportamiento de __del__ . Parece que mis puntos 1-4 siguen siendo válidos?


Actualización 2:

Algunas de las principales bibliotecas de python abarcan el uso de __del__ en el python post-PEP 442 (es decir, python 3.4+). Supongo que mi punto 3 ya no es válido después de PEP 442, y los otros puntos se aceptan como una complejidad inevitable de finalización de objeto.


1 __del__ la pregunta simplemente escribiendo un método __del__ personalizado para incluir la confianza en __del__ de stdlib.

2 Parece que __del__ siempre se __del__ en la salida de intérprete en las versiones más recientes de Cpython (¿alguien tiene un contraejemplo?). Sin embargo, no importa para el propósito de __del____del__ : los documentos no ofrecen explícitamente ninguna garantía sobre este comportamiento , por lo que no se puede confiar en él (puede cambiar en futuras versiones y puede ser diferente en intérpretes que no sean de CPython) ).

Los administradores de contexto (y los bloques try / finally ) son algo más restrictivos que __del__ . En general, requieren que usted estructure su código de tal manera que la vida útil del recurso que necesita liberar no se extienda más allá de una llamada de función en algún nivel de la stack de llamadas, en lugar de, por ejemplo, vincularla a la vida útil. de una instancia de clase que podría ser destruida en momentos y lugares impredecibles. Por lo general, es bueno restringir la vida útil de los recursos a un solo scope, pero a veces hay casos extremos donde este patrón es un ajuste incómodo.

El único caso en el que he usado __del__ (aparte de para la depuración, cf @ respuesta de MSeifert) es para liberar memoria asignada fuera de Python por una biblioteca externa. Debido al diseño de la biblioteca que estaba envolviendo, era difícil evitar tener un gran número de objetos que mantuvieran los punteros en la memoria asignada en el montón. El uso de un método __del__ para liberar los punteros fue la forma más fácil de realizar la limpieza, ya que no habría sido práctico encerrar la vida útil de cada instancia dentro de un administrador de contexto.

Un caso de uso es la depuración. Si desea realizar un seguimiento de la vida útil de un objeto específico, es conveniente escribir un método __del__ temporal . Se puede usar para hacer algún registro o simplemente para print algo. Lo he usado varias veces, especialmente cuando estaba interesado en cuándo y cómo se eliminan las instancias. A veces es bueno saber cuándo creas y descartas muchas instancias temporales. Pero como dije, solo usé esto para satisfacer mi curiosidad o al depurar.

Otro caso de uso es la subclasificación de una clase que define un método __del__ . A veces, encuentra una clase que desea subclasificar, pero las __del__ internas requieren que realmente anule __del__ para controlar el orden en que se limpia la instancia. Eso también es muy raro porque necesita encontrar una clase con __del__ , necesita subclasificarla y necesita introducir algunos elementos internos que realmente requieren llamar a la superclase __del__ en el momento exacto. De hecho, lo hice una vez, pero no recuerdo dónde y por qué era importante (tal vez ni siquiera sabía sobre alternativas, así que trate esto como un posible caso de uso).

Cuando envuelve un objeto externo (por ejemplo, un objeto c que no es rastreado por Python) que realmente necesita ser desasignado incluso si alguien “olvida” (sospecho que mucha gente simplemente lo omite a propósito) para usar el gestor de contexto que ha proporcionado.


Sin embargo, todos estos casos son (o deberían ser) muy, muy raros. En realidad, es un poco como con las metaclases: son divertidas y es genial entender los conceptos porque puedes probar las “partes divertidas” de python. Pero en la práctica:

Si se pregunta si los necesita [metaclases], no lo hace (las personas que realmente los necesitan saben con certeza que los necesitan y no necesitan una explicación sobre por qué).

Cita (probablemente) de Tim Peters (no he encontrado la referencia original).

Un caso en el que siempre uso __del__ , es para cerrar un objeto aiohttp.ClientSession .

Cuando no lo haga, aiohttp imprimirá advertencias sobre la sesión de cliente no cerrada.