Capítulo 11: Tratamento Robusto de Exceções, Exceções de Domínio e ExceptionGroup
Especialização em Sistemas com Python & POO com Type Hints • Capítulo 11
🗺️ Mapa Conceitual do Tópico
flowchart TD
subgraph PHILOSOPHY["Filosofia Operacional do Python"]
EAFP["EAFP (Easier to Ask for Forgiveness than Permission)\nAtômico contra Race Conditions (TOCTOU)"]
LBYL["LBYL (Look Before You Leap)\nChecagens Condicionais Prévias com Overhead de I/O"]
end
subgraph HIERARCHY["Árvore Genealógica de Exceções"]
BASE["BaseException (Sinais de Sistema: KeyboardInterrupt / SystemExit)"]
EXC["Exception (Base Mandatória de Aplicações de Negócio)"]
DOM["Hierarquia de Exceções de Domínio (DominioError)"]
end
subgraph MODERN_FEATURES["Recursos Modernos (Python 3.11+)"]
CHAIN["Encadeamento Explícito (raise ... from erro / PEP 3134)"]
EXC_GROUP["ExceptionGroup & Sintaxe except* (PEP 654)"]
NOTES["Notas Diagnósticas de Depuração (err.add_note() / PEP 678)"]
end
PHILOSOPHY --> HIERARCHY
BASE --> EXC
EXC --> DOM
DOM --> MODERN_FEATURES
style PHILOSOPHY fill:#ede7f6,stroke:#512da8,stroke-width:2px
style HIERARCHY fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
style MODERN_FEATURES fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
🛡️ 1. Paradigma EAFP vs LBYL e a Hierarquia de Exceções
No ecossistema Python, o tratamento de erros é guiado pelo axioma EAFP (Easier to Ask for Forgiveness than Permission), em oposição ao tradicional LBYL (Look Before You Leap):
- LBYL: Inspeciona previamente as condições antes de agir (
if os.path.exists(arq): open(arq)). Gera uma brecha de concorrência clássica chamada TOCTOU (Time-of-Check to Time-of-Use), onde o arquivo pode ser deletado por outro processo entre a verificação e a abertura física. - EAFP: Tenta a operação diretamente dentro de um bloco
trye captura a exceção específica com precisão atômica (try: open(arq) except FileNotFoundError:).
A Árvore de Tipos: Por que NUNCA Capturar BaseException?
BaseException
├── BaseExceptionGroup
├── GeneratorExit
├── KeyboardInterrupt (Ctrl+C do Terminal)
├── SystemExit (Chamada de sys.exit())
└── Exception (Base de todos os erros de software e domínio)
├── ArithmeticError (ZeroDivisionError, OverflowError)
├── LookupError (IndexError, KeyError)
├── ValueError, TypeError, RuntimeError
└── MinhasExcecoesDeDominio ...
⚠️ Regra de Ouro: Capturar
except BaseException:ouexcept:sem tipo captura acidentalmente oKeyboardInterrupteSystemExit, impedindo que administradores ou o Kubernetes consigam desligar graciosamente o processo comSIGINTouSIGTERM. Capture sempreexcept Exception:ou subclasses especializadas.
⚙️ 2. Anatomia Completa do Bloco try-except-else-finally
| Bloco | Condição de Execução | Propósito Arquitetural |
|---|---|---|
try |
Incondicional no início | Circunscreve o código de risco de I/O, rede ou validação. |
except TipoErro as err |
Apenas se TipoErro for lançado |
Trata o incidente, gera logs ou traduz para exceção de domínio. |
else |
Somente se NENHUMA exceção ocorrer | Executa o fluxo de sucesso, evitando manter código seguro dentro do try. |
finally |
Incondicional (Sempre executa) | Liberação determinística de recursos (fechamento de conexões e locks). |
🔗 3. Encadeamento Explícito de Exceções (PEP 3134)
Ao interceptar um erro de infraestrutura de baixo nível (ex.: um erro de socket psycopg2.OperationalError), uma aplicação corporativa deve traduzi-lo para uma exceção rica de domínio (ex.: FalhaConexaoBancoError). O encadeamento via from preserva o histórico de depuração:
try:
cursor.execute("SELECT * FROM transacoes")
except OperationalError as err_db:
# Vincula o traceback original em __cause__
raise RepositorioIndisponivelError("Falha no cluster de persistência.") from err_db
Se o erro original contiver dados confidenciais (senhas ou tokens), use raise NovoErro() from None para suprimir o contexto interno do traceback.
🚀 4. ExceptionGroup e a Sintaxe except* (PEP 654 / Python 3.11+)
Em processamento concorrente (asyncio com TaskGroup), processamento de lotes ou validação múltipla de formulários, um único fluxo pode gerar dezenas de exceções independentes simultaneamente. O Python 3.11 introduziu o ExceptionGroup e a instrução except*:
# Disparando múltiplos erros agregados em uma única estrutura:
erros = [
ValueError("CNPJ com dígitos verificadores inválidos"),
KeyError("Campo obrigatório 'email' ausente"),
TimeoutError("Servidor da Receita Federal demorou para responder")
]
raise ExceptionGroup("Falhas na validação cadastral", erros)
A instrução except* permite capturar e tratar subconjuntos de erros por tipo, permitindo que outros tipos continuem subindo a pilha:
try:
validar_lote_documentos()
except* ValueError as eg_val:
for err in eg_val.exceptions:
registrar_erro_validacao(err)
except* TimeoutError as eg_time:
acionar_circuito_de_retry(eg_time)
Anotações com add_note() (PEP 678)
O método err.add_note("Contexto extra") anexa metadados diagnósticos (como ID da transação ou IP do cliente) sem alterar a mensagem original da exceção, sendo exibido limpo no traceback do console.