Hierarquias de Exceções Personalizadas
Exceções de domínio agrupam falhas relacionadas para que os chamadores possam capturá-las na granularidade correta. Uma árvore rasa sob uma base específica do pacote é melhor do que dezenas de subclasses Exception não relacionadas.
Receita
class AppError(Exception):
"""Base para todos os erros do serviço."""
class ValidationError(AppError):
def __init__(self, field: str, message: str) -> None:
self.field = field
super().__init__(f"{field}: {message}")Quando usar isso:
- Camadas HTTP/API mapeiam tipos de exceção para códigos de status
- Chamadores precisam capturar todos os erros de pagamento, mas não todos os erros do aplicativo
- Bibliotecas exportam uma base de exceção pública estável
- Métricas/alertas são rotulados por classe de exceção
- Testes afirmam tipos de falha específicos
Exemplo de Trabalho
class BillingError(Exception):
"""Levantada para falhas do subsistema de faturamento."""
class CardDeclined(BillingError):
def __init__(self, last4: str, reason: str) -> None:
self.last4 = last4
self.reason = reason
super().__init__(f"cartão *{last4} recusado: {reason}")
class InsufficientFunds(CardDeclined):
pass
class InvoiceError(BillingError):
pass
def charge(card_last4: str, amount: float) -> None:
if amount > 1000:
raise InsufficientFunds(card_last4, "fundos insuficientes")
if amount < 0:
raise InvoiceError("valor negativo")
def handle_payment() -> str:
try:
charge("4242", 2000)
except CardDeclined as exc:
return f"tente novamente com um novo cartão ({exc.reason})"
except BillingError:
return "faturamento indisponível"
return "ok"
print(handle_payment())O que isso demonstra:
- A base do pacote
BillingErrorcaptura qualquer falha de faturamento CardDeclinedadiciona campos para manipuladores e logs- Subclasses especializam sem duplicar a lógica do manipulador
- Ramos separados para erros de faturamento recuperáveis vs. genéricos
Mergulho Profundo
Como Funciona
- Subclasse
Exception(nãoBaseException) para erros de aplicativo - Uma base em nível de módulo ou pacote por contexto delimitado
- Anexe atributos em
__init__antes desuper().__init__(message) - Mantenha a hierarquia rasa (base + 2-3 níveis)
- Documente quais exceções cruzam limites de pacotes
Diretrizes de Hierarquia
| Camada | Base | Exemplo |
|---|---|---|
| Biblioteca | LibraryError | ConnectionTimeout |
| Domínio do aplicativo | AppError | OrderNotFound |
| Adaptador HTTP | Mapear para status | 404, 422, 503 |
Armadilhas
except Exceptiongenérico mascarando bugs - manipuladores muito amplos ocultamKeyboardInterruptse mal escopados. Correção: capture sua base, depois tipos específicos.- Árvores de herança profundas - comportamento duplicado entre irmãos. Correção: composição de campos, não pilhas de subclasses.
- Erros apenas de string - análise de texto de mensagem em manipuladores. Correção: atributos estruturados (
field,code). - Vazamento de exceções internas - erros do driver de banco de dados chegam aos clientes da API. Correção: envolva na fronteira com
from exc. - Classes de exceção vazias sem documentação - os mantenedores adivinham quando levantar. Correção: docstring por classe com condições de levantamento.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Códigos estilo errno | API estável legível por máquina | Serviços internos Pythonicos |
| Pydantic ValidationError | Apenas análise de entrada | Violações de regras de negócios |
| Tipos de resultado | Módulos puramente funcionais | Framework espera levantamentos |
FAQs
Quantas exceções base um aplicativo deve ter?
Uma por contexto delimitado (faturamento, autenticação, inventário) mais uma base opcional para todo o aplicativo, se você precisar de uma captura universal em main().
Devo subclassificar ValueError?
Raramente para erros de domínio. Subclasse sua base de domínio para que os manipuladores não capturem ValueErrors não relacionados.
Como os frameworks HTTP usam hierarquias?
Registre manipuladores: @app.exception_handler(CardDeclined) retorna 402 com corpo estruturado.
Exceções dataclass são OK?
Sim em 3.11+ com dataclass em subclasses de Exception - certifique-se de que __init__ chame super corretamente.
Como nomear exceções?
Frases nominais para estado (UserNotFound) ou tempo passado de verbo para eventos (PaymentDeclined).
Bibliotecas devem reexportar exceções?
Sim em __all__ - a superfície da API pública inclui tipos de exceção estáveis.
Como migrar de Exception genérico?
Introduza a base, envolva levantamentos nas fronteiras, deprecie Exception genérico ao longo dos lançamentos.
E quanto ao ExceptionGroup?
Falhas concorrentes usam grupos; hierarquias de domínio ainda se aplicam dentro das exceções folha.
Preciso de códigos únicos?
Adicione .code: str quando os clientes ramificam programaticamente; o tipo de classe sozinho geralmente é suficiente em serviços Python.
Como testar hierarquias?
pytest.raises(BillingError) e assert isinstance(exc.value, CardDeclined) para especialização.
Relacionado
- Levantando & Encadeando - envolva com
from - Grupos de Exceção & except* - erros concorrentes
- Noções Básicas de Exceções - ordem de captura
- Melhores Práticas de Erros & Logging - falhe ruidosamente
Versões da Pilha: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.