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

    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    urllib.parse no Python: manipule URLs

    Aprenda urllib.parse no Python para decompor URLs, criar queries, codificar componentes e evitar riscos com urljoin, redirects e SSRF.

    Ler mais

    Tempo de leitura: 6 minutos
    18/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ipaddress no Python: redes IPv4 e IPv6

    Aprenda ipaddress no Python para validar IPv4 e IPv6, calcular redes CIDR, dividir sub-redes e criar políticas de acesso seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    18/08/2026
    A vibrant collection of blue sewing threads arranged with hands on a white background.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    queue no Python: coordene threads

    Aprenda queue no Python para coordenar threads com FIFO, prioridade, backpressure, task_done, join, retries e shutdown seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    17/08/2026
    A male software engineer working on code in a modern office setting.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    struct no Python: trabalhe com binário

    Aprenda struct no Python para empacotar dados binários, controlar endianness, usar buffers e validar protocolos e arquivos externos.

    Ler mais

    Tempo de leitura: 5 minutos
    17/08/2026
    Close-up of a laptop screen with code and a coffee mug, perfect for tech abstract themes.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tarfile no Python: crie TAR seguro

    Aprenda tarfile no Python para criar TAR comprimido, inspecionar membros e extrair com filtros, limites e proteção contra path traversal.

    Ler mais

    Tempo de leitura: 5 minutos
    17/08/2026
    Row of colorful office binders neatly arranged on a shelf, ideal for organization concepts.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    gzip no Python: comprima arquivos .gz

    Aprenda gzip no Python para ler e gravar .gz, criar saídas reproduzíveis, trabalhar com streams e limitar a expansão de

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026