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 try e 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: ou except: sem tipo captura acidentalmente o KeyboardInterrupt e SystemExit, impedindo que administradores ou o Kubernetes consigam desligar graciosamente o processo com SIGINT ou SIGTERM. Capture sempre except 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.