atexit no Python: execute limpeza ao sair

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

Scripts e serviços de linha de comando frequentemente precisam salvar estado, fechar relatórios, remover arquivos temporários ou registrar métricas quando terminam. O módulo atexit no Python permite cadastrar funções executadas automaticamente durante o encerramento normal do interpretador, reduzindo o risco de esquecer uma etapa de limpeza no caminho principal.

Neste guia, você aprenderá a usar register(), decorators, argumentos, ordem LIFO, unregister() e tratamento de exceções. O conteúdo complementa nossos artigos sobre context managers, tempfile, tracebacks, faulthandler e weakref.

Registrar uma função

atexit.register() adiciona uma função à lista de handlers de encerramento.

import atexit


def despedir():
    print("Aplicação encerrada")

atexit.register(despedir)

Quando o módulo principal termina normalmente ou sys.exit() é chamado, o handler é executado.

Usar como decorator

Como register() devolve a própria função, ele pode ser usado como decorator.

import atexit

@atexit.register
def salvar_metricas():
    print("Salvando métricas")

Esse formato funciona diretamente quando a função não exige argumentos. Para parâmetros, passe-os na chamada de registro.

Passar argumentos

def registrar_saida(nome, status="ok"):
    print(f"{nome}: {status}")

atexit.register(
    registrar_saida,
    "processamento",
    status="finalizado",
)

Os argumentos são armazenados no registro e usados somente durante o encerramento. Evite capturar objetos gigantes ou recursos que podem já estar em estado parcialmente destruído.

Ordem LIFO

Os handlers são chamados em ordem inversa ao registro. Se A, B e C forem cadastrados nessa sequência, a execução será C, B e A.

atexit.register(lambda: print("primeiro registrado"))
atexit.register(lambda: print("segundo registrado"))
atexit.register(lambda: print("terceiro registrado"))

A documentação oficial de atexit explica que módulos de baixo nível tendem a ser carregados antes e, portanto, são limpos por último.

Encerramento normal

Os handlers são executados quando o módulo principal chega ao fim ou quando sys.exit() gera SystemExit.

import sys

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

O código de saída não impede o handler. Contudo, a função não deve alterar silenciosamente o significado do status final.

Casos em que não executa

Atexit não é garantia absoluta. Os handlers não são chamados quando:

  • o processo recebe um sinal fatal não tratado pelo Python;
  • ocorre um erro interno fatal do interpretador;
  • os._exit() é usado;
  • o processo é encerrado externamente de forma abrupta;
  • a máquina perde energia.

Dados críticos precisam de gravação transacional durante a operação, não somente no final.

atexit e sinais

Se a aplicação precisa reagir a SIGTERM ou SIGINT, instale handlers com o módulo signal e inicie um encerramento controlado.

import signal
import sys


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

signal.signal(signal.SIGTERM, encerrar)
signal.signal(signal.SIGINT, encerrar)

O handler deve ser simples. A conversão para sys.exit() permite que o encerramento normal acione atexit.

Não usar os._exit()

os._exit() encerra imediatamente sem flush normal, finally, handlers de atexit ou callbacks de alto nível.

Ele é usado em situações específicas após fork() ou em falhas extremas. Não o escolha para sair de uma aplicação comum.

Exceções nos handlers

Se um handler gera uma exceção diferente de SystemExit, um traceback é exibido e os demais handlers continuam sendo executados.

Depois que todos têm oportunidade de rodar, a última exceção é relançada.

def falhar():
    raise RuntimeError("falha na limpeza")

atexit.register(falhar)

Por isso, cada etapa deve capturar e registrar erros recuperáveis sem impedir outras limpezas.

Handler resiliente

import logging

logger = logging.getLogger(__name__)


def fechar_relatorio():
    try:
        gerar_relatorio_final()
    except Exception:
        logger.exception("Não foi possível gerar o relatório final")

atexit.register(fechar_relatorio)

Evite esconder toda falha quando ela torna o encerramento incorreto. Defina quais erros são apenas diagnósticos e quais devem produzir status não zero.

Registrar a mesma função várias vezes

A mesma função e os mesmos argumentos podem ser cadastrados mais de uma vez. Cada ocorrência é executada.

def mostrar(nome):
    print(nome)

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

Esse comportamento pode gerar duplicidade em módulos importados repetidamente por sistemas de plugins. Proteja a inicialização com estado explícito.

Remover com unregister()

atexit.unregister() remove todas as ocorrências de uma função.

atexit.unregister(fechar_relatorio)

Se a função não estava registrada, nada acontece. A remoção usa comparação por igualdade, não apenas identidade.

Implicações da igualdade

Objetos chamáveis podem definir __eq__(). Duas instâncias consideradas iguais podem ser removidas juntas.

class Acao:
    def __init__(self, nome):
        self.nome = nome

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

    def __eq__(self, outro):
        return isinstance(outro, Acao) and self.nome == outro.nome

Para handlers importantes, prefira funções simples ou mantenha referências claras.

Alterar registros durante a limpeza

Registrar ou remover handlers enquanto um handler está sendo executado possui efeito indefinido.

Monte a lista durante a inicialização e não a modifique no encerramento. Se a ordem precisa ser dinâmica, registre um único coordenador que chama uma lista própria.

Coordenador de limpeza

acoes = []


def adicionar_acao(funcao):
    acoes.append(funcao)


def executar_acoes():
    for funcao in reversed(acoes):
        try:
            funcao()
        except Exception:
            logger.exception("Falha em ação de limpeza")

atexit.register(executar_acoes)

Esse padrão centraliza ordem, logging e métricas, mas continua sujeito às limitações de encerramento abrupto.

Não iniciar threads

A partir do Python 3.12, tentar iniciar uma nova thread dentro de um handler gera RuntimeError.

import threading


def incorreto():
    thread = threading.Thread(target=trabalho)
    thread.start()

Durante o shutdown, o runtime já pode estar liberando estados internos. Inicie e finalize workers antes de chegar à fase de atexit.

Não usar fork()

Também desde Python 3.12, chamar os.fork() em um handler gera RuntimeError.

Criação de processos durante a desmontagem pode causar race conditions e crashes. Toda tarefa assíncrona de encerramento deve ser concluída antes ou realizada por um supervisor externo.

Threads existentes

Atexit não substitui um protocolo de parada. A thread principal deve sinalizar workers, aguardar join() e só então terminar.

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

O handler pode registrar que uma thread permaneceu viva, mas não deve iniciar nova infraestrutura.

Context managers são preferíveis

Recursos locais devem ser fechados com with.

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

O bloco libera o recurso assim que ele deixa de ser necessário, inclusive em exceções. Atexit deve ser uma camada final para recursos de vida global.

try/finally

Quando a aplicação controla claramente o loop principal, try/finally torna a ordem explícita.

iniciar()
try:
    executar_loop()
finally:
    encerrar()

Esse padrão é mais fácil de testar. Atexit é útil quando a inicialização ocorre em módulo e não existe um ponto único de encerramento acessível.

Arquivos temporários

Objetos de tempfile normalmente oferecem context managers e limpeza automática. Use atexit apenas como fallback para recursos globais.

Não presuma que a remoção sempre acontecerá. Armazene temporários em diretórios seguros que possam ser limpos na próxima inicialização.

Salvar estado

O exemplo clássico é persistir um contador.

from pathlib import Path
import atexit

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


def salvar():
    temporario = caminho.with_suffix(".tmp")
    temporario.write_text(str(contador), encoding="utf-8")
    temporario.replace(caminho)

atexit.register(salvar)

A escrita temporária e substituição reduz arquivos parcialmente gravados, mas não garante execução em crash.

Logging no encerramento

A ordem dos handlers e a desmontagem de módulos podem afetar logging. Registre o handler depois de configurar o logger e garanta que os destinos permaneçam disponíveis.

Evite depender de globals que possam ter sido alterados. Capture referências diretas necessárias durante o registro.

Subinterpretadores

Desde Python 3.7, registros feitos por extensões C em subinterpretadores são locais ao interpretador em que foram criados.

Ferramentas embutidas e runtimes com múltiplos interpretadores devem registrar limpeza no contexto correto.

Testar handlers

Não encerre o processo principal do conjunto de testes. Extraia a lógica para uma função comum e teste-a diretamente.

def salvar_estado():
    ...

atexit.register(salvar_estado)


def test_salvar_estado(tmp_path):
    salvar_estado()

Para testar a integração real, execute um subprocesso e verifique arquivo, saída e status após o término.

Subprocesso de teste

import subprocess
import sys

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

assert resultado.returncode == 0
assert "limpeza concluída" in resultado.stdout

Adicione cenários com sys.exit(), exceção não tratada e sinal convertido em saída controlada.

Aplicações web

Servidores possuem hooks próprios de startup e shutdown. Use o ciclo de vida do framework para fechar pools, filas e clientes.

Atexit pode atuar como fallback local, mas workers podem ser terminados abruptamente pelo orquestrador. A lógica principal deve estar no mecanismo oficial do servidor.

Containers e Kubernetes

Containers recebem SIGTERM e possuem um período antes de SIGKILL. O processo PID 1 deve tratar o sinal, parar de aceitar trabalho, concluir operações e sair normalmente.

Atexit será executado somente se esse caminho controlado chegar ao encerramento do Python antes do limite.

Erros frequentes

  • Usar atexit como única proteção de dados críticos.
  • Esperar execução após os._exit() ou SIGKILL.
  • Iniciar thread ou processo no handler.
  • Registrar a mesma função várias vezes sem perceber.
  • Modificar registros durante a limpeza.
  • Depender de logging já encerrado.
  • Usar atexit no lugar de with.
  • Executar tarefas longas no shutdown.

Boas práticas

  • Registre handlers curtos e idempotentes.
  • Use LIFO conscientemente.
  • Prefira context managers e finally.
  • Não inicie threads nem faça fork.
  • Proteja gravações com arquivos temporários.
  • Teste a lógica diretamente e em subprocesso.
  • Integre sinais e ciclo de vida do framework.
  • Planeje recuperação após encerramento abrupto.

Conclusão

O módulo atexit no Python registra funções executadas em ordem inversa durante o encerramento normal. Ele é útil para métricas finais, persistência leve e limpeza de recursos globais que não possuem um ponto de finalização melhor.

A garantia é limitada: sinais fatais, os._exit(), crashes e perda de energia ignoram os handlers. Com funções curtas, idempotência, context managers e um protocolo explícito de shutdown, atexit funciona como uma última camada de organização, não como substituto de durabilidade.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Monitor com código binário representando análise de opcodes pickle com pickletools no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pickletools no Python: analise pickles

    Aprenda pickletools no Python para desmontar pickles, analisar opcodes e otimizar fluxos sem executar dados não confiáveis.

    Ler mais

    Tempo de leitura: 7 minutos
    05/08/2026
    Código-fonte em tela representando análise com tokenize no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tokenize no Python: analise código-fonte

    Aprenda tokenize no Python para ler tokens, comentários, indentação, codificação, posições e reconstruir código-fonte com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    05/08/2026
    Desenvolvedor trabalhando em automação de build e compilação de diretórios com compileall no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    compileall no Python: compile diretórios

    Aprenda compileall no Python para compilar diretórios, gerar pyc em paralelo, filtrar arquivos e controlar otimização e invalidação.

    Ler mais

    Tempo de leitura: 7 minutos
    04/08/2026
    Monitor com código binário representando geração de arquivos pyc com py_compile no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    py_compile no Python: gere arquivos pyc

    Aprenda py_compile no Python para gerar arquivos pyc, validar sintaxe e controlar otimização e invalidação por timestamp ou hash.

    Ler mais

    Tempo de leitura: 7 minutos
    04/08/2026
    Editor de código representando correção de tabs e espaços com tabnanny no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tabnanny no Python: corrija indentação

    Aprenda tabnanny no Python para detectar tabs e espaços ambíguos, verificar projetos e evitar TabError e IndentationError.

    Ler mais

    Tempo de leitura: 7 minutos
    04/08/2026
    Código-fonte e sintaxe representando análise lexical com tokenize no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tokenize no Python: analise código-fonte

    Aprenda tokenize no Python para analisar tokens, comentários, encoding, posições e reconstruir código-fonte com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    04/08/2026