atexit en Python: ejecuta limpieza al salir

Publicado el: 05/08/2026
Tempo de leitura: 7 minutos

Las herramientas de terminal y los servicios a menudo necesitan guardar estado, cerrar reportes, eliminar archivos temporales o registrar métricas cuando terminan. El módulo atexit en Python permite registrar funciones que se ejecutan automáticamente durante el cierre normal del intérprete, reduciendo el riesgo de olvidar una etapa final.

Esta guía explica register(), decorators, argumentos, orden LIFO, unregister() y manejo de excepciones. Complementa nuestros artículos sobre context managers, tempfile, tracebacks, faulthandler y weakref.

Registrar una función

atexit.register() añade un callable a la lista de cierre.

import atexit


def despedir():
    print("Aplicación finalizada")

atexit.register(despedir)

Cuando el módulo principal termina normalmente o se llama a sys.exit(), el handler se ejecuta.

Usarlo como decorator

Como register() devuelve la función original, puede utilizarse como decorator.

@atexit.register
def guardar_metricas():
    print("Guardando métricas")

Esta forma funciona directamente para funciones sin argumentos. Para parámetros, pásalos en la llamada de registro.

Pasar argumentos

def registrar_salida(nombre, estado="ok"):
    print(f"{nombre}: {estado}")

atexit.register(
    registrar_salida,
    "procesamiento",
    estado="finalizado",
)

Los argumentos permanecen referenciados hasta el cierre. Evita capturar grafos enormes o recursos que puedan estar parcialmente desmontados.

Orden LIFO

Los handlers se ejecutan en orden inverso al registro. Si A, B y C se registran en ese orden, se llaman C, B y A.

atexit.register(lambda: print("registrado primero"))
atexit.register(lambda: print("registrado segundo"))
atexit.register(lambda: print("registrado tercero"))

La documentación oficial de atexit explica que los módulos de bajo nivel suelen cargarse antes y limpiarse después.

Finalización normal

Los handlers se ejecutan cuando el módulo principal llega al final o sys.exit() genera SystemExit.

import sys

atexit.register(lambda: print("limpieza"))
sys.exit(0)

El código de salida no impide la limpieza. Un handler no debería ocultar el status final previsto.

Casos en los que no se ejecuta

Atexit no es una garantía absoluta. Los handlers se omiten cuando:

  • el proceso recibe una señal fatal no manejada por Python;
  • el intérprete sufre un error interno fatal;
  • se usa os._exit();
  • el sistema mata el proceso abruptamente;
  • la máquina pierde energía.

Los datos críticos necesitan persistencia transaccional durante la operación, no únicamente al final.

Señales y cierre controlado

Las aplicaciones que responden a SIGTERM o SIGINT deberían instalar handlers con signal e iniciar una salida controlada.

import signal
import sys


def detener(signum, frame):
    sys.exit(128 + signum)

signal.signal(signal.SIGTERM, detener)
signal.signal(signal.SIGINT, detener)

El handler de señal debe ser simple. Convertir la señal en SystemExit permite recorrer el cierre normal.

Evitar os._exit()

os._exit() termina inmediatamente sin flush normal, bloques finally, handlers de atexit ni limpieza de alto nivel.

Existe para casos especiales de control de procesos. No debe ser la salida habitual de una aplicación.

Excepciones en handlers

Si un handler genera una excepción distinta de SystemExit, se imprime un traceback y los handlers restantes todavía tienen oportunidad de ejecutarse.

Después, la última excepción se vuelve a lanzar.

def fallar():
    raise RuntimeError("falló la limpieza")

atexit.register(fallar)

Cada etapa debe decidir qué errores puede registrar y tolerar sin bloquear otras tareas.

Handler resistente

import logging

logger = logging.getLogger(__name__)


def cerrar_reporte():
    try:
        generar_reporte_final()
    except Exception:
        logger.exception("No fue posible generar el reporte")

atexit.register(cerrar_reporte)

No ocultes toda falla cuando el cierre incorrecto debe afectar el status del proceso.

Registrar varias veces

La misma función y argumentos pueden registrarse más de una vez. Cada ocurrencia se ejecuta.

def mostrar(nombre):
    print(nombre)

atexit.register(mostrar, "A")
atexit.register(mostrar, "B")

Los sistemas de plugins pueden duplicar registros accidentalmente. Protege la inicialización con estado explícito.

Eliminar con unregister()

atexit.unregister() elimina todas las ocurrencias de una función coincidente.

atexit.unregister(cerrar_reporte)

Si no estaba registrada, no ocurre nada. La comparación utiliza igualdad y no solamente identidad.

Implicaciones de la igualdad

Los objetos callables pueden definir __eq__(). Varias instancias consideradas iguales pueden eliminarse juntas.

class Accion:
    def __init__(self, nombre):
        self.nombre = nombre

    def __call__(self):
        print(self.nombre)

    def __eq__(self, otro):
        return isinstance(otro, Accion) and self.nombre == otro.nombre

Para handlers importantes, prefiere funciones simples o conserva referencias claras.

No modificar registros durante el cierre

Registrar o eliminar funciones mientras se ejecutan handlers tiene efectos indefinidos.

Construye la lista durante el startup. Si el orden debe ser dinámico, registra un coordinador que maneje su propia colección.

Coordinador de limpieza

acciones = []


def agregar_accion(funcion):
    acciones.append(funcion)


def ejecutar_acciones():
    for funcion in reversed(acciones):
        try:
            funcion()
        except Exception:
            logger.exception("Falló una acción de limpieza")

atexit.register(ejecutar_acciones)

El patrón centraliza orden, logging y métricas, pero mantiene las mismas limitaciones ante cierres abruptos.

No iniciar threads

Desde Python 3.12, iniciar una thread desde un handler genera RuntimeError.

import threading


def incorrecto():
    thread = threading.Thread(target=trabajo)
    thread.start()

El runtime puede estar liberando estados internos. Inicia y detén workers antes de llegar a la fase de atexit.

No llamar fork()

También desde Python 3.12, llamar a os.fork() en un handler genera RuntimeError.

Crear procesos mientras el intérprete se desmonta puede causar races y crashes. Finaliza tareas asíncronas antes o delega al supervisor.

Threads existentes

Atexit no reemplaza un protocolo de shutdown. La thread principal debería señalar workers, esperar join() y luego terminar.

stop_event.set()
for thread in threads:
    thread.join(timeout=5)

El handler puede registrar que un worker sigue vivo, pero no debe crear infraestructura nueva.

Preferir context managers

Los recursos locales deberían cerrarse con with.

with open("salida.txt", "w", encoding="utf-8") as archivo:
    archivo.write("resultado")

El recurso se libera al dejar de necesitarlo, incluso ante excepciones. Atexit debe ser una capa final para recursos globales.

Preferir try/finally

Cuando la aplicación controla el loop principal, try/finally hace explícito el orden.

iniciar()
try:
    ejecutar_loop()
finally:
    detener()

Este patrón es más fácil de probar. Atexit ayuda cuando la inicialización ocurre dentro de módulos.

Archivos temporales

Los objetos de tempfile normalmente poseen context managers y limpieza automática. Usa atexit solamente como fallback para recursos globales.

No supongas que la eliminación siempre ocurrirá. Guarda temporales en una ubicación segura que pueda limpiarse en el siguiente inicio.

Persistir estado ligero

Un ejemplo clásico guarda un contador.

from pathlib import Path
import atexit

ruta = Path("contador.txt")
contador = int(ruta.read_text()) if ruta.exists() else 0


def guardar():
    temporal = ruta.with_suffix(".tmp")
    temporal.write_text(str(contador), encoding="utf-8")
    temporal.replace(ruta)

atexit.register(guardar)

La escritura temporal reduce archivos parciales, pero no garantiza ejecución durante un crash.

Logging durante el cierre

El orden y la desmontagem de módulos pueden afectar logging. Registra el handler después de configurar el logger y mantén disponibles los destinos.

Evita depender de globals que otros handlers puedan modificar. Captura referencias directas necesarias.

Subintérpretes

Desde Python 3.7, los registros realizados por extensiones C son locales al subintérprete en el que fueron creados.

Los runtimes embebidos con varios intérpretes deben instalar limpieza en el contexto correcto.

Probar handlers

No termines el proceso principal de tests. Extrae la lógica a funciones ordinarias y pruébalas directamente.

def guardar_estado():
    ...

atexit.register(guardar_estado)


def test_guardar_estado(tmp_path):
    guardar_estado()

Para integración real, inicia un subprocess y verifica archivos, salida y status después de terminar.

Test en subprocess

import subprocess
import sys

resultado = subprocess.run(
    [sys.executable, "app_prueba.py"],
    capture_output=True,
    text=True,
)

assert resultado.returncode == 0
assert "limpieza completa" in resultado.stdout

Añade escenarios con sys.exit(), excepción no manejada y señales convertidas en cierre controlado.

Aplicaciones web

Los servidores y frameworks tienen hooks de startup y shutdown. Usa el ciclo oficial para cerrar pools, colas, clientes y workers.

Atexit puede ser fallback local, pero un orquestador puede terminar workers abruptamente. La limpieza principal pertenece al framework.

Containers y Kubernetes

Los containers reciben SIGTERM y un periodo antes de SIGKILL. PID 1 debe tratar la señal, dejar de aceptar trabajo, concluir operaciones y salir normalmente.

Atexit solo se ejecuta si ese camino controlado llega al cierre de Python antes del límite.

Errores frecuentes

  • Usar atexit como única protección de datos críticos.
  • Esperarlo después de os._exit() o SIGKILL.
  • Iniciar thread o proceso en un handler.
  • Registrar una función varias veces por accidente.
  • Cambiar registros durante la limpieza.
  • Depender de logging ya finalizado.
  • Usar atexit en lugar de with.
  • Ejecutar tareas largas al cerrar.

Buenas prácticas

  • Registra handlers cortos e idempotentes.
  • Usa el orden LIFO conscientemente.
  • Prefiere context managers y finally.
  • No inicies threads ni uses fork.
  • Protege escrituras con archivos temporales.
  • Prueba lógica directa y en subprocess.
  • Integra señales y ciclo del framework.
  • Planifica recuperación tras cierre abrupto.

Conclusión

El módulo atexit en Python registra funciones ejecutadas en orden inverso durante el cierre normal. Es útil para métricas finales, persistencia ligera y recursos globales que no tienen un mejor punto de cierre.

La garantía es limitada: señales fatales, os._exit(), crashes y pérdida de energía omiten los handlers. Con funciones cortas, idempotencia, context managers y un protocolo explícito, atexit funciona como una capa final de organización y no como sustituto de durabilidad.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Monitor con código binario que representa análisis de opcodes pickle con pickletools en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickletools en Python: analiza pickles

    Aprende pickletools en Python para desmontar pickles, analizar opcodes y optimizar flujos sin ejecutar datos no confiables.

    Ler mais

    Tempo de leitura: 6 minutos
    05/08/2026
    Código fuente en pantalla que representa análisis con tokenize en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tokenize en Python: analiza código fuente

    Aprende tokenize en Python para inspeccionar tokens, comentarios, indentación, codificación, posiciones y reconstruir código de forma segura.

    Ler mais

    Tempo de leitura: 7 minutos
    05/08/2026
    Desarrollador trabajando en automatización de build y compilación de directorios con compileall en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    compileall en Python: compila directorios

    Aprende compileall en Python para compilar directorios, generar pyc en paralelo, filtrar rutas y controlar optimización e invalidación.

    Ler mais

    Tempo de leitura: 6 minutos
    04/08/2026
    Monitor con código binario que representa generación de archivos pyc con py_compile en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    py_compile en Python: genera archivos pyc

    Aprende py_compile en Python para generar archivos pyc, validar sintaxis y controlar optimización e invalidación por timestamp o hash.

    Ler mais

    Tempo de leitura: 6 minutos
    04/08/2026
    Editor de código que representa corrección de tabs y espacios con tabnanny en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tabnanny en Python: corrige la indentación

    Aprende tabnanny en Python para detectar tabs y espacios ambiguos, revisar proyectos y evitar TabError e IndentationError.

    Ler mais

    Tempo de leitura: 7 minutos
    04/08/2026
    Código fuente y sintaxis que representan análisis léxico con tokenize en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tokenize en Python: analiza código fuente

    Aprende tokenize en Python para analizar tokens, comentarios, encoding, posiciones y reconstruir código fuente con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    04/08/2026