Python Criando suas Exceções

O Python já vem com dezenas de exceções: ValueError, KeyError, ZeroDivisionError… Elas falam dos problemas do Python: um texto que não vira número, uma chave que não existe, uma divisão por zero. Mas o seu programa tem problemas próprios. Na lojinha da Ana, "acabou o café" e "esse produto a gente não vende" são erros que nenhuma exceção do Python descreve direito. Nesta aula você vai criar as suas exceções, com nome, mensagem e até dados, para que o resto do programa saiba exatamente o que deu errado.

Você vai aprender
  • Por que usar ValueError para tudo acaba misturando erros diferentes.
  • A criar uma exceção com class NomeError(Exception):, e o costume do Error no fim do nome.
  • A lançá-la com raise e a capturá-la com except.
  • A montar uma família de exceções (uma mãe, várias filhas) e a capturar todas de uma vez.
  • Por que a ordem dos except importa.
  • A guardar dados dentro da exceção (o produto, a quantidade) e a montar a mensagem com super().__init__.
  • A trocar um erro do Python por um seu com raise … from e.
  • Os erros reais: exceptions must derive from BaseException, a mensagem que vira tupla, o except com nome errado.

A ideia: alarmes com etiqueta

Imagine que a lojinha tivesse um alarme só, que toca por qualquer motivo: porta aberta, fumaça, freezer quente. Quando ele toca, a Ana tem de sair procurando o que foi. Agora imagine um painel com uma luz para cada problema: "freezer", "porta dos fundos", "fumaça". Ela olha e já sabe aonde ir. E ainda dá para ter uma luz geral, "problema na loja", que acende junto com qualquer uma das outras.

Cada exceção que você cria é uma dessas luzes com etiqueta. E a família de exceções (seção 4) é a luz geral.

Antes de começar, lembre: raise lança uma exceção (aula raise: Lançando Erros) e try/except a captura (aula try e except). Aqui você vai juntar as duas coisas com classes e herança.


1. O problema: um ValueError para tudo

A função vender recusa a venda quando não tem estoque. Sem exceção própria, a Ana usou o ValueError. A quantidade vem digitada como texto, então o programa também converte com int():

estoque = {"café": 2, "leite": 10}


def vender(produto, quantidade):
    if quantidade > estoque[produto]:
        raise ValueError(f"só temos {estoque[produto]} unidades de {produto}")
    estoque[produto] -= quantidade


for texto in ["5", "dois"]:
    try:
        vender("café", int(texto))
    except ValueError as e:
        print("Não deu:", e)
$ python3 generico.py
Não deu: só temos 2 unidades de café
Não deu: invalid literal for int() with base 10: 'dois'

Os dois problemas caíram no mesmo except. Um é "falta café" (o cliente pode levar menos); o outro é "digitaram errado" (é preciso perguntar de novo). Eles pedem reações diferentes, mas o programa não tem como separá-los, porque o int("dois") também lança ValueError. A saída é dar ao problema do estoque uma etiqueta só dele.


2. A primeira exceção própria

class EstoqueInsuficienteError(Exception):
    pass


estoque = {"café": 2, "leite": 10}


def vender(produto, quantidade):
    if quantidade > estoque[produto]:
        raise EstoqueInsuficienteError(f"só temos {estoque[produto]} unidades de {produto}")
    estoque[produto] -= quantidade


vender("leite", 3)
print("Leite vendido. Sobraram", estoque["leite"])
vender("café", 5)
print("Esta linha não roda.")
class EstoqueInsuficienteError
Uma exceção é uma classe. O nome segue o costume das classes (cada palavra com maiúscula) e termina em Error ("erro"), como recomenda o guia de estilo do Python (aula PEP 8), para que quem lê saiba na hora que aquilo é um erro.
(Exception)
A parte mais importante: ela herda de Exception, a mãe de quase todas as exceções do Python. É isso que permite usá-la com raise e except.
pass
"Não faça nada." A classe não precisa de mais nada: herdou tudo o que uma exceção sabe fazer, inclusive guardar a mensagem.
raise EstoqueInsuficienteError(f"…")
Cria um objeto da exceção, com a mensagem entre parênteses, e o lança, como você faria com raise ValueError("…").
$ python3 primeira.py
Leite vendido. Sobraram 7
Traceback (most recent call last):
  File "/home/ana/primeira.py", line 16, in <module>
    vender("café", 5)
    ~~~~~~^^^^^^^^^^^
  File "/home/ana/primeira.py", line 10, in vender
    raise EstoqueInsuficienteError(f"só temos {estoque[produto]} unidades de {produto}")
EstoqueInsuficienteError: só temos 2 unidades de café

O leite foi vendido normalmente. No café, a sua exceção apareceu num traceback igualzinho aos do Python: a linha 16 chamou vender, a linha 10 lançou o erro, e a última linha traz o nome da sua classe e a sua mensagem: EstoqueInsuficienteError: só temos 2 unidades de café. Como ninguém capturou, o programa parou, e o último print não rodou.


3. Capturando a sua exceção

Agora cada problema tem o seu except:

class EstoqueInsuficienteError(Exception):
    pass


estoque = {"café": 2, "leite": 10}


def vender(produto, quantidade):
    if quantidade > estoque[produto]:
        raise EstoqueInsuficienteError(f"só temos {estoque[produto]} unidades de {produto}")
    estoque[produto] -= quantidade


for texto in ["5", "dois", "1"]:
    try:
        vender("café", int(texto))
        print(f"Vendi {texto} café. Sobraram {estoque['café']}.")
    except EstoqueInsuficienteError as e:
        print("Falta produto:", e)
    except ValueError:
        print(f"Quantidade inválida: {texto!r}")
$ python3 capturar.py
Falta produto: só temos 2 unidades de café
Quantidade inválida: 'dois'
Vendi 1 café. Sobraram 1.

Três pedidos, três destinos: o "5" caiu no except EstoqueInsuficienteError, o "dois" caiu no except ValueError (lançado pelo int()), e o "1" foi vendido. O as e guarda o objeto da exceção, e print(e) mostra a mensagem que você passou no raise.

Missão: o programa lança e captura a EstoqueInsuficienteError, mas esqueceram de criar a classe (rode para ver o NameError). Crie a exceção no começo do arquivo.

estoque = {"café": 2}


def vender(produto, quantidade):
    if quantidade > estoque[produto]:
        raise EstoqueInsuficienteError(f"só temos {estoque[produto]} unidades de {produto}")
    estoque[produto] -= quantidade


try:
    vender("café", 5)
except EstoqueInsuficienteError as e:
    print("Falta produto:", e)
class EstoqueInsuficienteError(Exception):
    pass


estoque = {"café": 2}


def vender(produto, quantidade):
    if quantidade > estoque[produto]:
        raise EstoqueInsuficienteError(f"só temos {estoque[produto]} unidades de {produto}")
    estoque[produto] -= quantidade


try:
    vender("café", 5)
except EstoqueInsuficienteError as e:
    print("Falta produto:", e)

4. Uma família de exceções

A lojinha tem mais de um problema próprio: além do estoque, alguém pode pedir um produto que a loja não vende. A Ana cria uma exceção mãe, LojaError, e as outras herdam dela:

class LojaError(Exception):
    """Erro de qualquer regra da lojinha."""


class EstoqueInsuficienteError(LojaError):
    """Pediram mais do que tem no estoque."""


class ProdutoNaoEncontradoError(LojaError):
    """O produto não existe no cadastro."""


estoque = {"café": 2, "leite": 10}


def vender(produto, quantidade):
    if produto not in estoque:
        raise ProdutoNaoEncontradoError(f"não vendemos {produto}")
    if quantidade > estoque[produto]:
        raise EstoqueInsuficienteError(f"só temos {estoque[produto]} unidades de {produto}")
    estoque[produto] -= quantidade


for produto, quantidade in [("café", 5), ("pizza", 1), ("leite", 1)]:
    try:
        vender(produto, quantidade)
        print(f"Vendido: {quantidade} {produto}")
    except LojaError as e:
        print(f"{type(e).__name__}: {e}")

print(issubclass(EstoqueInsuficienteError, LojaError))
print(issubclass(LojaError, Exception))
class LojaError(Exception):
A mãe da família. Herda de Exception.
"""Erro de qualquer regra da lojinha."""
Uma docstring (aula Docstrings) explicando para que serve a exceção. Ela já basta como corpo da classe, no lugar do pass.
class EstoqueInsuficienteError(LojaError):
Uma filha: herda de LojaError (e, por tabela, de Exception).
except LojaError as e:
Captura a LojaError e todas as filhas dela.
type(e).__name__
O nome da classe da exceção que chegou, para mostrar qual foi.
issubclass(A, B)
"A é filha (ou neta…) de B?"
$ python3 hierarquia.py
EstoqueInsuficienteError: só temos 2 unidades de café
ProdutoNaoEncontradoError: não vendemos pizza
Vendido: 1 leite
True
True

Um except só pegou os dois tipos de problema. É a luz geral do painel: quem chama vender e quer tratar "qualquer problema da loja" escreve except LojaError; quem quer tratar só a falta de estoque escreve except EstoqueInsuficienteError. E nenhum dos dois engole por engano um ValueError ou um KeyError que não tem nada a ver com as regras da loja.

O Python organiza as exceções dele do mesmo jeito. Por exemplo, KeyError e IndexError são filhas de LookupError ("erro de busca"), e as duas são netas de Exception:

Exception
├── LookupError
│   ├── KeyError
│   └── IndexError
├── ValueError
└── LojaError
    ├── EstoqueInsuficienteError
    └── ProdutoNaoEncontradoError

Missão: o except LojaError deveria pegar os dois erros da loja, mas o programa para com um traceback. O problema está na família: faça as duas exceções filhas herdarem de LojaError.

class LojaError(Exception):
    pass


class EstoqueInsuficienteError(Exception):
    pass


class ProdutoNaoEncontradoError(Exception):
    pass


estoque = {"café": 2, "leite": 10}


def vender(produto, quantidade):
    if produto not in estoque:
        raise ProdutoNaoEncontradoError(f"não vendemos {produto}")
    if quantidade > estoque[produto]:
        raise EstoqueInsuficienteError(f"só temos {estoque[produto]} unidades de {produto}")
    estoque[produto] -= quantidade


for produto, quantidade in [("café", 5), ("pizza", 1), ("leite", 1)]:
    try:
        vender(produto, quantidade)
        print(f"Vendido: {quantidade} {produto}")
    except LojaError as e:
        print(f"{type(e).__name__}: {e}")
class LojaError(Exception):
    pass


class EstoqueInsuficienteError(LojaError):
    pass


class ProdutoNaoEncontradoError(LojaError):
    pass


estoque = {"café": 2, "leite": 10}


def vender(produto, quantidade):
    if produto not in estoque:
        raise ProdutoNaoEncontradoError(f"não vendemos {produto}")
    if quantidade > estoque[produto]:
        raise EstoqueInsuficienteError(f"só temos {estoque[produto]} unidades de {produto}")
    estoque[produto] -= quantidade


for produto, quantidade in [("café", 5), ("pizza", 1), ("leite", 1)]:
    try:
        vender(produto, quantidade)
        print(f"Vendido: {quantidade} {produto}")
    except LojaError as e:
        print(f"{type(e).__name__}: {e}")

5. A ordem dos except: da filha para a mãe

Quando há vários except, o Python testa um por um, de cima para baixo, e usa o primeiro que servir. Como a mãe serve para todas as filhas, colocá-la primeiro esconde as outras:

class LojaError(Exception):
    pass


class EstoqueInsuficienteError(LojaError):
    pass


try:
    raise EstoqueInsuficienteError("só temos 2 unidades de café")
except LojaError as e:
    print("Erro da loja:", e)
except EstoqueInsuficienteError as e:
    print("Falta produto:", e)
$ python3 ordem_except.py
Erro da loja: só temos 2 unidades de café

O erro era de estoque, mas quem tratou foi o except LojaError. O segundo except nunca vai rodar, e o Python não avisa. Regra: escreva os except do mais específico (a filha) para o mais geral (a mãe).


6. Exceções que carregam dados

Uma mensagem em texto é boa para mostrar, mas ruim para fazer contas. Se o programa quiser saber quantas unidades faltaram, ele teria de ler a frase. Melhor guardar os dados na própria exceção, como atributos:

class EstoqueInsuficienteError(Exception):
    def __init__(self, produto, pedido, disponivel):
        self.produto = produto
        self.pedido = pedido
        self.disponivel = disponivel
        super().__init__(f"pediram {pedido} {produto}, mas só temos {disponivel}")


estoque = {"café": 2}


def vender(produto, quantidade):
    if quantidade > estoque[produto]:
        raise EstoqueInsuficienteError(produto, quantidade, estoque[produto])
    estoque[produto] -= quantidade


try:
    vender("café", 5)
except EstoqueInsuficienteError as e:
    print("Mensagem:", e)
    print("Faltam", e.pedido - e.disponivel, "unidades de", e.produto)
    print(e.args)

vender("café", 7)
def __init__(self, produto, pedido, disponivel):
A exceção agora recebe três dados, em vez de uma mensagem pronta (aula __init__ e self).
self.produto = produto …
Guarda cada dado num atributo, como em qualquer classe.
super().__init__(f"…")
Chama o __init__ da mãe, Exception, com a mensagem montada a partir dos dados. É essa mensagem que o print(e) e o traceback mostram. O super() é da aula Herança.
raise EstoqueInsuficienteError(produto, quantidade, estoque[produto])
Quem lança passa os dados; a exceção monta a frase sozinha. Assim a mensagem sai sempre no mesmo formato.
e.pedido - e.disponivel
Quem captura usa os dados direto, sem ler a frase.
e.args
Uma tupla com o que foi passado ao __init__ da Exception: aqui, só a mensagem.
$ python3 atributos.py
Mensagem: pediram 5 café, mas só temos 2
Faltam 3 unidades de café
('pediram 5 café, mas só temos 2',)
Traceback (most recent call last):
  File "/home/ana/atributos.py", line 25, in <module>
    vender("café", 7)
    ~~~~~~^^^^^^^^^^^
  File "/home/ana/atributos.py", line 14, in vender
    raise EstoqueInsuficienteError(produto, quantidade, estoque[produto])
EstoqueInsuficienteError: pediram 7 café, mas só temos 2

Dentro do try, o programa mostrou a mensagem e fez a conta com os atributos. Na última linha, sem try, o traceback mostrou a mensagem montada pelo super().__init__. Esquecer essa linha tem um efeito estranho, que você vai ver em Quando dá errado.


7. Trocando um erro do Python por um seu: raise … from e

A função preco_de procura o preço num dicionário. Se o produto não existe, o dicionário lança KeyError, mas para quem usa a lojinha seria melhor receber um ProdutoNaoEncontradoError. Dá para capturar um e lançar o outro, deixando registrado que um causou o outro:

class ProdutoNaoEncontradoError(Exception):
    pass


precos = {"café": 18.9, "leite": 5.49}


def preco_de(produto):
    try:
        return precos[produto]
    except KeyError as e:
        raise ProdutoNaoEncontradoError(f"não vendemos {produto}") from e


print(preco_de("café"))
print(preco_de("pizza"))
$ python3 from_e.py
18.9
Traceback (most recent call last):
  File "/home/ana/from_e.py", line 10, in preco_de
    return precos[produto]
           ~~~~~~^^^^^^^^^
KeyError: 'pizza'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/ana/from_e.py", line 16, in <module>
    print(preco_de("pizza"))
          ~~~~~~~~^^^^^^^^^
  File "/home/ana/from_e.py", line 12, in preco_de
    raise ProdutoNaoEncontradoError(f"não vendemos {produto}") from e
ProdutoNaoEncontradoError: não vendemos pizza

Agora são dois tracebacks, ligados por uma frase:

  • O primeiro é o erro original: KeyError: 'pizza', na linha 10.
  • The above exception was the direct cause of the following exception: "a exceção acima foi a causa direta da exceção abaixo". É o from e que cria essa ligação.
  • O segundo é o seu erro, ProdutoNaoEncontradoError: não vendemos pizza, lançado na linha 12. É ele que um except de fora vai receber.

Sem o from e, o Python também mostra os dois, mas com outra frase: During handling of the above exception, another exception occurred ("durante o tratamento da exceção acima, aconteceu outra"). Ela dá a entender que o seu except é que falhou, quando na verdade você trocou o erro de propósito. O from e deixa clara a intenção.


Formas diferentes de fazer a mesma coisa

SituaçãoUse
O problema já tem uma exceção do Python que descreve bem (um argumento com valor inválido)A do Python: raise ValueError("…")
Um problema das regras do seu programa (estoque, saldo, cadastro)Uma exceção própria: class EstoqueInsuficienteError(Exception): pass
Vários problemas do mesmo assunto, que às vezes são tratados juntosUma mãe (LojaError) e filhas
Quem captura precisa de números ou nomes, não só de uma fraseAtributos no __init__ + super().__init__(mensagem)
Um erro do Python que você quer apresentar como erro seuraise SeuError("…") from e

Uma exceção própria também pode herdar de uma exceção do Python, em vez de Exception: class PrecoInvalidoError(ValueError). Assim ela é capturada tanto por except PrecoInvalidoError quanto por um except ValueError que já existia no programa.


Quando dá errado

Esqueceu de herdar de Exception
class EstoqueInsuficienteError:
    pass


raise EstoqueInsuficienteError()
$ python3 sem_heranca.py
Traceback (most recent call last):
  File "/home/ana/sem_heranca.py", line 5, in <module>
    raise EstoqueInsuficienteError()
TypeError: exceptions must derive from BaseException

"Exceções precisam derivar de BaseException." Sem o (Exception), é uma classe comum, e o raise só aceita objetos da família das exceções. (O BaseException é a avó de todas: a mãe da Exception.) Se você tentar passar uma mensagem, raise EstoqueInsuficienteError("…"), o erro chega antes, ao criar o objeto: TypeError: EstoqueInsuficienteError() takes no arguments, porque uma classe comum sem __init__ não aceita argumentos. Como consertar: class EstoqueInsuficienteError(Exception):.

A mensagem saiu como uma tupla
class EstoqueInsuficienteError(Exception):
    def __init__(self, produto, pedido, disponivel):
        self.produto = produto
        self.pedido = pedido
        self.disponivel = disponivel


try:
    raise EstoqueInsuficienteError("café", 5, 2)
except EstoqueInsuficienteError as e:
    print("Mensagem:", e)
$ python3 sem_super.py
Mensagem: ('café', 5, 2)

Sem erro, mas a mensagem virou ('café', 5, 2). Faltou o super().__init__(mensagem): a Exception não recebeu frase nenhuma e, para não ficar calada, mostra os argumentos que a exceção recebeu, numa tupla. Como consertar: no fim do __init__, super().__init__(f"pediram {pedido} {produto}, mas só temos {disponivel}").

Herdou de BaseException: o except Exception não pega
class EstoqueInsuficienteError(BaseException):
    pass


try:
    raise EstoqueInsuficienteError("só temos 2 unidades de café")
except Exception as e:
    print("Peguei:", e)
print("Fim")
$ python3 base_exception.py
Traceback (most recent call last):
  File "/home/ana/base_exception.py", line 6, in <module>
    raise EstoqueInsuficienteError("só temos 2 unidades de café")
EstoqueInsuficienteError: só temos 2 unidades de café

O except Exception não pegou, e o programa parou sem chegar no Fim. A BaseException está acima da Exception: ela é reservada para casos especiais, como o Ctrl+C (KeyboardInterrupt) e o sys.exit() (SystemExit), que de propósito escapam de um except Exception. Como consertar: as suas exceções herdam sempre de Exception (ou de uma filha dela).

Nome errado no except: o erro só aparece quando a exceção chega
class EstoqueInsuficienteError(Exception):
    pass


try:
    print("Vendendo...")
except EstoqueInsuficienteErro:
    print("Falta produto")
print("Sem erro, o except nem foi lido.")

try:
    raise EstoqueInsuficienteError("só temos 2 unidades de café")
except EstoqueInsuficienteErro:
    print("Falta produto")
$ python3 nome_errado_except.py
Vendendo...
Sem erro, o except nem foi lido.
Traceback (most recent call last):
  File "/home/ana/nome_errado_except.py", line 12, in <module>
    raise EstoqueInsuficienteError("só temos 2 unidades de café")
EstoqueInsuficienteError: só temos 2 unidades de café

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/ana/nome_errado_except.py", line 13, in <module>
    except EstoqueInsuficienteErro:
           ^^^^^^^^^^^^^^^^^^^^^^^
NameError: name 'EstoqueInsuficienteErro' is not defined. Did you mean: 'EstoqueInsuficienteError'?

Nos dois except, falta o r final de Error. No primeiro try, nada deu errado, e o Python nem leu o nome do except: ele só olha o que está no except quando uma exceção chega. No segundo, a exceção chegou, o Python foi conferir o nome, e aí veio o NameError, com a frase "durante o tratamento da exceção acima, aconteceu outra" e a sugestão do nome certo. É um defeito traiçoeiro: o programa pode rodar por semanas até o dia em que o erro acontece de verdade. Como consertar: confira o nome (o "Did you mean" ajuda) e teste o caminho do erro, provocando a exceção de propósito.

A mãe antes da filha

Você já viu na seção 5: com except LojaError antes de except EstoqueInsuficienteError, o segundo nunca roda, sem aviso nenhum. Como consertar: do mais específico para o mais geral.


Referência rápida

EscritaO que faz
class NomeError(Exception): passCria uma exceção própria
class FilhaError(MaeError): passCria uma exceção filha (capturada também por except MaeError)
raise NomeError("mensagem")Lança a exceção com uma mensagem
except NomeError as e:Captura a exceção (e as filhas dela)
print(e) / str(e)A mensagem
e.argsA tupla do que foi passado à Exception
type(e).__name__O nome da classe da exceção
def __init__(self, …): … super().__init__(mensagem)Exceção com atributos e mensagem montada
raise NomeError("…") from eTroca uma exceção por outra, registrando a causa
issubclass(Filha, Mae)Confere a família
SintomaCausa provável
TypeError: exceptions must derive from BaseExceptionA classe não herda de Exception
TypeError: NomeError() takes no arguments no raiseClasse comum (sem herança) recebendo mensagem
A mensagem aparece como ('café', 5, 2)__init__ próprio sem super().__init__(mensagem)
O except Exception não pega a sua exceçãoEla herda de BaseException
Um except nunca roda, sem avisoUm except da mãe está antes dele
NameError no except, só quando o erro aconteceNome da exceção digitado errado no except
The above exception was the direct cause…Não é defeito: foi um raise … from e mostrando a causa

Exercícios

1. A mãe certa

Complete para criar a exceção da lojinha:

class PrecoInvalidoError():
    pass

2. Lançando

Complete a palavra que lança a exceção:

PrecoInvalidoError("o preço não pode ser negativo")

3. EstoqueInsuficienteError e ProdutoNaoEncontradoError herdam de LojaError. Qual except pega as duas?

4. Por que o except LojaError deve vir depois do except EstoqueInsuficienteError?

5. A sua exceção tem um __init__ próprio, e print(e) mostra ('café', 5, 2). O que faltou?

6. Qual é o costume para o nome de uma exceção?

Missão final: o saque sem saldo

Missão: a conta tem R$ 50 e a Ana tentou sacar R$ 80. A exceção SaldoInsuficienteError recebe o saldo e o valor, mas não guarda nada (rode: a mensagem sai como tupla e o e.valor dá erro). Escreva o __init__ dela: guarde saldo e valor em atributos e monte uma mensagem com super().__init__(…).

class SaldoInsuficienteError(Exception):
    pass  # troque: guarde o saldo e o valor, e monte a mensagem


def sacar(saldo, valor):
    if valor > saldo:
        raise SaldoInsuficienteError(saldo, valor)
    return saldo - valor


try:
    sacar(50, 80)
except SaldoInsuficienteError as e:
    print("Não deu:", e)
    print(f"Faltam R$ {e.valor - e.saldo:.2f}")
class SaldoInsuficienteError(Exception):
    def __init__(self, saldo, valor):
        self.saldo = saldo
        self.valor = valor
        super().__init__(f"saque de R$ {valor:.2f}, mas o saldo é R$ {saldo:.2f}")


def sacar(saldo, valor):
    if valor > saldo:
        raise SaldoInsuficienteError(saldo, valor)
    return saldo - valor


try:
    sacar(50, 80)
except SaldoInsuficienteError as e:
    print("Não deu:", e)
    print(f"Faltam R$ {e.valor - e.saldo:.2f}")

Com a solução, o lab mostrou:

$ python3 main.py
Não deu: saque de R$ 80.00, mas o saldo é R$ 50.00
Faltam R$ 30.00

Resumo

  • Uma exceção própria é uma classe que herda de Exception: class EstoqueInsuficienteError(Exception): pass. O nome termina em Error.
  • Ela é lançada com raise e capturada com except, como as do Python, e separa os problemas do seu programa dos problemas do Python.
  • Uma exceção mãe (LojaError) com filhas permite tratar todas juntas ou cada uma separada. Nos except, a filha vem antes da mãe.
  • Para carregar dados, escreva um __init__ com atributos e chame super().__init__(mensagem); sem isso, a mensagem vira uma tupla.
  • raise SeuError(…) from e troca um erro do Python pelo seu e registra a causa ("direct cause").
  • Nunca herde de BaseException: um except Exception não pegaria a sua exceção.