Python raise e Exceções Personalizadas
Aprenda a usar o raise do Python, encadear exceções com raise...from e criar classes de exceção personalizadas para um tratamento de erros claro e sustentável.
Python permite fazer mais do que capturar erros — você também pode sinalizar erros deliberadamente com a instrução raise e criar seus próprios tipos de exceção para representar problemas específicos do domínio. Este capítulo baseia-se em Python Try...Except e aborda:
- A instrução
raise— lançando exceções embutidas - Relançando exceções dentro de um bloco
except - Encadeamento de exceções com
raise ... from - Criando classes de exceção personalizadas
- Construindo uma hierarquia de exceções para uma aplicação real
- A instrução
asserte quando utilizá-la
A Instrução raise
A instrução raise permite lançar uma exceção em qualquer ponto do seu código. A forma mais comum passa uma instância de exceção com uma mensagem descritiva:
raise ExceptionType("message")Use raise quando seu código detectar um problema que o chamador deve tratar. Por exemplo, uma função que aceita uma idade deve rejeitar valores negativos imediatamente, em vez de prosseguir silenciosamente:
def set_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
return age
try:
set_age(-1)
except ValueError as e:
print(e)
# Output: Age cannot be negativeEscolhendo a Exceção Embutida Correta
Os tipos de exceção embutidos do Python carregam significado. Escolher o tipo correto torna sua API mais fácil de entender e permite que os chamadores tratem diferentes categorias de erro separadamente.
| Exceção | Quando lançar |
|---|---|
ValueError | O argumento tem o tipo correto, mas um valor inválido (age = -1) |
TypeError | O argumento tem o tipo errado (age = "old") |
KeyError | Uma chave de dicionário obrigatória está ausente |
IndexError | Um índice de sequência está fora do intervalo |
FileNotFoundError | Um arquivo obrigatório não existe |
PermissionError | O processo não tem os direitos para realizar uma operação |
RuntimeError | Um problema geral de tempo de execução que não se encaixa em um tipo mais específico |
NotImplementedError | Um método existe em uma classe base, mas deve ser substituído |
Lançar ValueError para um valor errado é muito mais informativo do que lançar uma Exception genérica, porque os chamadores podem escrever except ValueError para tratar exatamente esse caso.
Relançando uma Exceção
Às vezes você quer fazer algo com uma exceção — registrá-la, liberar um recurso — e depois deixar a mesma exceção se propagar para o chamador sem alterações. Chame raise sem argumentos dentro de um bloco except para relançar a exceção atual:
def read_config(path):
try:
with open(path) as f:
return f.read()
except FileNotFoundError:
print(f"Warning: config file not found at {path}")
raise # re-raise the original FileNotFoundError
try:
read_config("missing.cfg")
except FileNotFoundError as e:
print(f"Caught: {e}")
# Output:
# Warning: config file not found at missing.cfg
# Caught: [Errno 2] No such file or directory: 'missing.cfg'Usar raise simples preserva o traceback original, o que torna a depuração muito mais fácil do que capturar e relançar e como uma nova exceção.
Encadeamento de Exceções com raise ... from
Quando você captura uma exceção e lança uma diferente, Python automaticamente registra a exceção original como o contexto da nova. Você pode tornar esse relacionamento explícito — e significativo — usando raise NovaExceção from original:
def load_data(path):
try:
with open(path) as f:
return f.read()
except OSError as e:
raise RuntimeError("Failed to load configuration") from e
try:
load_data("config.json")
except RuntimeError as e:
print(f"Error: {e}")
print(f"Caused by: {e.__cause__}")
# Output:
# Error: Failed to load configuration
# Caused by: [Errno 2] No such file or directory: 'config.json'Quando Python exibe o traceback, ele mostra ambas as exceções em ordem, deixando claro que o RuntimeError foi consequência direta do OSError. Isso é especialmente útil em código de bibliotecas, onde você deseja traduzir erros de baixo nível do sistema operacional em erros de domínio de nível mais alto sem esconder a causa raiz.
Suprimindo o Encadeamento com raise ... from None
Ocasionalmente, a exceção original é um detalhe de implementação que você não deseja expor. Passe None como causa para ocultá-la:
def fetch(url):
try:
raise ConnectionError("timeout")
except ConnectionError:
raise RuntimeError("Network unavailable") from None
try:
fetch("http://example.com")
except RuntimeError as e:
print(f"Error: {e}")
print(f"Cause hidden: {e.__cause__}")
# Output:
# Error: Network unavailable
# Cause hidden: NoneO traceback mostrará apenas o RuntimeError. Use isso com moderação — ocultar a causa raiz dificulta a depuração para os consumidores da biblioteca.
Criando Classes de Exceção Personalizadas
As exceções embutidas cobrem erros de programação comuns, mas são genéricas demais para problemas de domínio. Se sua aplicação de e-commerce lançar um simples ValueError quando um pagamento falhar, os chamadores não conseguirão distingui-lo de um argumento de função inválido. Classes de exceção personalizadas resolvem isso.
Uma exceção personalizada é simplesmente uma classe que herda de Exception (ou uma de suas subclasses):
class InsufficientFundsError(Exception):
"""Raised when a bank account has insufficient funds."""
def __init__(self, amount, balance):
self.amount = amount
self.balance = balance
super().__init__(
f"Cannot withdraw {amount}: balance is only {balance}"
)
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(amount, self.balance)
self.balance -= amount
return self.balance
account = BankAccount(100)
try:
account.withdraw(150)
except InsufficientFundsError as e:
print(e)
print(f"You tried to withdraw: {e.amount}")
print(f"Available balance: {e.balance}")
# Output:
# Cannot withdraw 150: balance is only 100
# You tried to withdraw: 150
# Available balance: 100Pontos principais sobre esse padrão:
super().__init__(message)define a string legível por humanos retornada porstr(e).- Atributos extras (
self.amount,self.balance) permitem que os chamadores acessem dados estruturados da exceção, não apenas uma string. - Uma docstring clara documenta quando a exceção deve ser lançada.
Construindo uma Hierarquia de Exceções
Aplicações reais frequentemente têm muitos tipos de erros relacionados. Agrupá-los sob uma classe base compartilhada permite que os chamadores capturem o erro específico ou toda a categoria:
class AppError(Exception):
"""Base class for all application errors."""
class ValidationError(AppError):
"""Raised when user input fails validation."""
class DatabaseError(AppError):
"""Raised when a database operation fails."""
def validate_username(name):
if len(name) < 3:
raise ValidationError(f"Username '{name}' is too short (min 3 chars)")
try:
validate_username("ab")
except ValidationError as e:
print(f"Validation failed: {e}")
except AppError as e:
print(f"Application error: {e}")
# Output:
# Validation failed: Username 'ab' is too short (min 3 chars)Um chamador que só quer capturar erros de banco de dados pode escrever except DatabaseError. Um chamador que quer capturar qualquer problema da sua biblioteca pode escrever except AppError. Isso espelha o design da própria hierarquia de exceções do Python, onde OSError agrupa FileNotFoundError, PermissionError e vários outros.
Diretrizes para Exceções Personalizadas
- Herde de
Exception, não deBaseException.BaseExceptioné a raiz da hierarquia do Python e também incluiSystemExiteKeyboardInterrupt, que não devem ser capturados acidentalmente. - Termine o nome da classe em
Errorpara exceções que sinalizam um problema. Isso segue a nomenclatura do próprio Python (ValueError,TypeError,IOError). - Mantenha a classe mínima a menos que precise de atributos extras. Um corpo vazio com uma docstring é perfeitamente válido.
- Coloque as exceções em um módulo dedicado (por exemplo,
exceptions.py) em projetos maiores, para que os chamadores possam importá-las sem precisar importar o restante do seu código.
A Instrução assert
assert é uma forma leve de expressar invariantes — condições que devem ser verdadeiras para que seu código esteja correto:
def divide(a, b):
assert b != 0, "Divisor must not be zero"
return a / b
try:
divide(10, 0)
except AssertionError as e:
print(f"AssertionError: {e}")
print(divide(10, 2))
# Output:
# AssertionError: Divisor must not be zero
# 5.0assert condição, mensagem lança AssertionError com a mensagem fornecida quando condição é False.
Limitação importante: Python remove as instruções assert quando executado com a flag -O (otimização). Isso significa:
- Use
assertapenas para verificações de consistência interna e auxílios de depuração. - Use
raisecom uma exceção adequada para validação de entrada voltada ao usuário e verificações de API pública que devem sempre ser executadas.
Erros Comuns
Capturar e ignorar exceções silenciosamente
# Bad — the error disappears
try:
result = risky_operation()
except Exception:
pass
# Better — at minimum, log or re-raise
try:
result = risky_operation()
except Exception as e:
print(f"Operation failed: {e}")
raiseLançar uma string em vez de uma exceção
# Wrong — strings are not exceptions
raise "something went wrong" # TypeError
# Correct
raise ValueError("something went wrong")Capturar BaseException acidentalmente
# Dangerous — this catches KeyboardInterrupt and SystemExit too
except BaseException:
...
# Use Exception instead
except Exception:
...Resumo
| Técnica | Quando usar |
|---|---|
raise ExceptionType("msg") | Sinalizar um problema que o chamador deve tratar |
raise (simples) | Relançar a exceção atual após registrar ou limpar |
raise NewError(...) from original | Traduzir um erro de baixo nível em um de nível mais alto, preservando a causa |
raise NewError(...) from None | Traduzir um erro ocultando a causa interna |
| Classe de exceção personalizada | Dar a erros específicos do domínio um tipo único e capturável |
| Hierarquia de exceções | Permitir que os chamadores capturem categorias de erros específicas ou amplas |
assert | Verificar invariantes internas apenas durante o desenvolvimento |
Para uma visão completa sobre captura e tratamento de exceções, consulte Python Try...Except. Para entender como exceções personalizadas se encaixam no design de classes, revise Python Classes and Objects e Python Inheritance.