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.nomePara 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.stdoutAdicione 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.






