W3docs

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 assert e 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 negative

Escolhendo 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çãoQuando lançar
ValueErrorO argumento tem o tipo correto, mas um valor inválido (age = -1)
TypeErrorO argumento tem o tipo errado (age = "old")
KeyErrorUma chave de dicionário obrigatória está ausente
IndexErrorUm índice de sequência está fora do intervalo
FileNotFoundErrorUm arquivo obrigatório não existe
PermissionErrorO processo não tem os direitos para realizar uma operação
RuntimeErrorUm problema geral de tempo de execução que não se encaixa em um tipo mais específico
NotImplementedErrorUm 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: None

O 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:     100

Pontos principais sobre esse padrão:

  • super().__init__(message) define a string legível por humanos retornada por str(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 de BaseException. BaseException é a raiz da hierarquia do Python e também inclui SystemExit e KeyboardInterrupt, que não devem ser capturados acidentalmente.
  • Termine o nome da classe em Error para 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.0

assert 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 assert apenas para verificações de consistência interna e auxílios de depuração.
  • Use raise com 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}")
    raise

Lanç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écnicaQuando 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 originalTraduzir um erro de baixo nível em um de nível mais alto, preservando a causa
raise NewError(...) from NoneTraduzir um erro ocultando a causa interna
Classe de exceção personalizadaDar a erros específicos do domínio um tipo único e capturável
Hierarquia de exceçõesPermitir que os chamadores capturem categorias de erros específicas ou amplas
assertVerificar 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.

Prática

Prática
Which statement correctly raises a ValueError with the message 'invalid input'?
Which statement correctly raises a ValueError with the message 'invalid input'?
Prática
What does bare raise (with no argument) do inside an except block?
What does bare raise (with no argument) do inside an except block?
Prática
Which base class should a custom exception inherit from?
Which base class should a custom exception inherit from?
Was this page helpful?