atexit — Devoluciones de llamada de salida para programas¶
Propósito: | Registra funciones a las que se llamará cuando un programa finalice. |
---|
El módulo atexit
proporciona una interfaz para registrar funciones a las
que se llamará cuando un programa se finalice normalmente.
Registro de devoluciones de llamada de salida¶
Este es un ejemplo de registro de una función explícitamente llamando a
register()
.
import atexit
def all_done():
print('all_done()')
print('Registering')
atexit.register(all_done)
print('Registered')
Debido a que el programa no hace nada más, all_done()
se llama de
inmediato.
$ python3 atexit_simple.py
Registering
Registered
all_done()
También es posible registrar más de una función y pasar argumentos a las funciones registradas. Eso puede ser útil para desconectarse limpiamente de las bases de datos, eliminar archivos temporales, etc. En lugar de mantener una lista de recursos que deben liberarse, se puede registrar una función de limpieza separada para cada recurso.
import atexit
def my_cleanup(name):
print('my_cleanup({})'.format(name))
atexit.register(my_cleanup, 'first')
atexit.register(my_cleanup, 'second')
atexit.register(my_cleanup, 'third')
Las funciones de salida se invocan al revés del orden en que se registran.
Este método permite limpiar los módulos en el orden inverso desde el que se
importan (y, por lo tanto, registran sus funciones atexit
), lo que debería
reducir los conflictos de dependencia.
$ python3 atexit_multiple.py
my_cleanup(third)
my_cleanup(second)
my_cleanup(first)
Sintaxis del decorador¶
Las funciones que no requieren argumentos pueden registrarse utilizando
register()
como decorador. Esta sintaxis alternativa es conveniente para
las funciones de limpieza que operan en datos globales a nivel de módulo.
import atexit
@atexit.register
def all_done():
print('all_done()')
print('starting main program')
Debido a que la función se registra tal como está definida, también es importante asegurarse de que funciona correctamente incluso si el módulo no realiza ningún otro trabajo. Si los recursos que se supone que debe limpiar nunca se inicializaron, llamar a la devolución de llamada de salida no debería producir un error.
$ python3 atexit_decorator.py
starting main program
all_done()
Cancelar devoluciones de llamada¶
Para cancelar una devolución de llamada de salida, elimínala del registro
usando unregister()
.
import atexit
def my_cleanup(name):
print('my_cleanup({})'.format(name))
atexit.register(my_cleanup, 'first')
atexit.register(my_cleanup, 'second')
atexit.register(my_cleanup, 'third')
atexit.unregister(my_cleanup)
Todas las llamadas a la misma devolución de llamada se cancelan, independientemente de cuántas veces se haya registrado.
$ python3 atexit_unregister.py
Eliminar una devolución de llamada que no se registró previamente no se considera un error.
import atexit
def my_cleanup(name):
print('my_cleanup({})'.format(name))
if False:
atexit.register(my_cleanup, 'never registered')
atexit.unregister(my_cleanup)
Debido a que ignora silenciosamente las devoluciones de llamada desconocidas,
unregister()
puede usarse incluso cuando la secuencia de registros puede no
ser conocida.
$ python3 atexit_unregister_not_registered.py
¿Cuándo no se llaman las devoluciones de llamada de atexit?¶
Las devoluciones de llamada registradas con atexit
no se invocan si se
cumple alguna de estas condiciones:
- El programa termina debido a una señal.
os._exit()
se invoca directamente.- Se detectó un error fatal en el intérprete.
Un ejemplo de la sección subprocess
se puede actualizar para mostrar lo
que sucede cuando un programa es terminado por una señal. Hay dos archivos
involucrados, los programas padre e hijo. El padre inicia al hijo, hace una
pausa y luego lo termina.
import os
import signal
import subprocess
import time
proc = subprocess.Popen('./atexit_signal_child.py')
print('PARENT: Pausing before sending signal...')
time.sleep(1)
print('PARENT: Signaling child')
os.kill(proc.pid, signal.SIGTERM)
El hijo configura una devolución de llamada atexit
y luego duerme hasta que
llega la señal.
import atexit
import time
import sys
def not_called():
print('CHILD: atexit handler should not have been called')
print('CHILD: Registering atexit handler')
sys.stdout.flush()
atexit.register(not_called)
print('CHILD: Pausing to wait for signal')
sys.stdout.flush()
time.sleep(5)
Cuando se ejecuta, ésta es la salida.
$ python3 atexit_signal_parent.py
CHILD: Registering atexit handler
CHILD: Pausing to wait for signal
PARENT: Pausing before sending signal...
PARENT: Signaling child
El niño no imprime el mensaje embebido en not_called
.
Si un programa usa os._exit()
, puede evitar que se invoquen las
devoluciones de llamada atexit
.
import atexit
import os
def not_called():
print('This should not be called')
print('Registering')
atexit.register(not_called)
print('Registered')
print('Exiting...')
os._exit(0)
Como este ejemplo omite la ruta de salida normal, la devolución de llamada no
se ejecuta. La salida de impresión tampoco se vacía, por lo que el ejemplo se
ejecuta con la opción -u
para habilitar la E/S sin búfer.
$ python3 -u atexit_os_exit.py
Registering
Registered
Exiting...
Para garantizar que se ejecuten las devoluciones de llamada, permite que el
programa finalice agotando las instrucciones para ejecutar o llamando a
sys.exit()
.
import atexit
import sys
def all_done():
print('all_done()')
print('Registering')
atexit.register(all_done)
print('Registered')
print('Exiting...')
sys.exit()
Este ejemplo llama a sys.exit()
, por lo que se invocan las devoluciones de
llamada registradas.
$ python3 atexit_sys_exit.py
Registering
Registered
Exiting...
all_done()
Manejo de excepciones¶
Los rastreos para las excepciones generadas en las devoluciones de llamada
atexit
se imprimen en la consola y la última excepción planteada se vuelve
a generar para ser el mensaje de error final del programa.
import atexit
def exit_with_exception(message):
raise RuntimeError(message)
atexit.register(exit_with_exception, 'Registered first')
atexit.register(exit_with_exception, 'Registered second')
La orden de registro controla la orden de ejecución. Si un error en una devolución de llamada introduce un error en otra (registrado anteriormente, pero llamado más tarde), el mensaje de error final podría no ser el mensaje de error más útil para mostrar al usuario.
$ python3 atexit_exception.py
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
File "atexit_exception.py", line 11, in exit_with_exception
raise RuntimeError(message)
RuntimeError: Registered second
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
File "atexit_exception.py", line 11, in exit_with_exception
raise RuntimeError(message)
RuntimeError: Registered first
Por lo general, es mejor manejar y registrar silenciosamente todas las excepciones en las funciones de limpieza, ya que es complicado tener errores de volcado de programa al salir.
Ver también
- Documentación de la biblioteca estándar para atexit
- Manejo de excepciones – Manejo global de excepciones no detectadas.
- Notas para portar Python 2 a 3 para atexit