A classe Dinheiro tinha uma decisão a tomar quando as moedas eram diferentes. A escolha foi: imprimir um aviso com print e devolver None. Funcionou na demonstração, mas a gente marcou ali mesmo que voltaria — porque essa escolha tem um custo escondido.
Devolver None é como entregar um troco errado e torcer pra ninguém conferir. O problema não some — só vira responsabilidade de quem recebeu.
Dinheiro teria que escrever if resultado is None. Esquecer um é fácil.None segue pelo programa e só estoura quando alguém faz resultado.valor — longe da causa real.print no meio de centenas de linhas de log não é um obstáculo, é só ruído.A aula de hoje resolve isso na raiz: em vez de avisar e seguir, a função levanta uma exceção — para tudo na origem e obriga quem chamou a lidar com o problema.
Uma exceção é um objeto que representa uma situação anormal durante a execução. Quando o Python (ou você) detecta algo que foge do esperado, ele levanta uma exceção: a execução normal para e o controle sobe pela pilha de chamadas, procurando alguém disposto a tratar aquele erro.
int("abc")) ou você (raise).try/except compatível na pilha captura a exceção e decide o que fazer.Exceção é pro excepcional. Não use pra controlar fluxo normal e previsível — pra isso existe o if. Verificar se uma lista está vazia antes de mexer nela é um if; reagir a um arquivo que sumiu no meio da leitura é uma exceção.
# Exagero: usar exceção pra um caso previsível
try:
primeiro = lista[0]
except IndexError:
primeiro = None
# Melhor: um if resolve o previsível
primeiro = lista[0] if lista else None Regra prática: se você consegue prever e checar a condição com um if barato, use o if. Guarde a exceção pro que é genuinamente inesperado.
Tudo o que você aprendeu sobre herança (aulas 07 a 10) vale para exceções. No topo está BaseException; logo abaixo, Exception, de onde descem quase todas as exceções que você vai tratar. Capturar uma classe-mãe captura automaticamente todas as filhas.
BaseException
└── Exception
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── ValueError
├── TypeError
└── OSError
└── FileNotFoundError except KeyError pega só erro de chave. É o ideal — você sabe o que está tratando.except LookupError pega KeyError e IndexError juntos. Útil quando o tratamento é o mesmo pros dois.Exception: pega quase tudo. Use só na borda do programa (o main), pra registrar e não deixar o usuário ver um traceback cru.BaseException: ela inclui coisas como KeyboardInterrupt (Ctrl+C) e SystemExit — você não quer engolir esses.O Python testa os except de cima pra baixo e usa o primeiro que casa. Por isso, coloque os mais específicos primeiro e os mais genéricos por último — senão a mãe captura tudo antes de a filha ter chance.
try:
valor = dados[chave]
except KeyError: # específico primeiro
print("Chave não encontrada")
except Exception: # genérico por último
print("Outro erro qualquer") As exceções nativas (ValueError, TypeError) são genéricas. Quando o erro é específico do seu domínio — saldo insuficiente, pedido já cancelado, estoque vazio — uma exceção com esse nome torna o código autoexplicativo e permite capturar exatamente aquele caso, sem confundir com um ValueError qualquer.
class PedidoCanceladoError(Exception):
pass Só isso já funciona: herda de Exception, pode ser levantada com raise e capturada com except. O pass diz "não preciso de nada além do que Exception já oferece".
class SaldoInsuficienteError(Exception):
def __init__(self, saldo, valor):
self.saldo = saldo
self.valor = valor
self.falta = valor - saldo
super().__init__(
f"Faltam R$ {self.falta:.2f} para sacar R$ {valor:.2f}"
) saldo, valor, falta) ficam disponíveis em quem captura: except ... as erro e depois erro.falta.super().__init__(mensagem) entrega o texto pra classe-mãe — é o que aparece no print(erro) e no traceback.Error (SaldoInsuficienteError), seguindo o padrão das exceções nativas do Python.Em sistemas maiores, vale criar uma exceção-base do seu domínio e fazer as específicas herdarem dela. Assim, quem usa seu código pode capturar tudo de uma vez (a base) ou caso a caso (as filhas).
class BancoError(Exception):
pass
class SaldoInsuficienteError(BancoError):
pass
class ContaBloqueadaError(BancoError):
pass
# captura qualquer erro do banco de uma vez:
# except BancoError as erro: ... O erro mais comum (e mais perigoso) no tratamento de exceções é capturar e ignorar. O bloco abaixo é um campo minado: ele faz qualquer bug desaparecer da tela enquanto continua quebrando o programa por dentro.
# ❌ NUNCA faça isso
try:
processar()
except:
pass except ValueError a except Exception. Capturar largo demais esconde bugs que você nem imaginava (um nome digitado errado vira NameError engolido).try curto: envolva só a linha que pode falhar. Um try gigante esconde qual linha realmente deu erro.raise sozinho pra deixar a exceção subir.import logging
try:
salvar_no_banco(pedido)
except ConnectionError as erro:
logging.error(f"Falha ao salvar: {erro}")
raise # registra E deixa subir — não engole Tratar exceção não é fazer o erro sumir. É decidir, conscientemente, o que acontece quando o esperado não acontece.
Programação Orientada a Objetos com Python
Tecnologia em Análise e Desenvolvimento de Sistemas
plano de voo
Por que devolver None ou imprimir um aviso é uma forma frágil de tratar erro.
Como try/except separa o que detecta o erro do que o trata.
Como criar suas próprias exceções para falar a língua do seu sistema.
Sistema bancário com SaldoInsuficienteError e validação de operações.
de onde paramos · 2 dias atrás
default, *args e **kwargs no lugar de várias versões fixas. __add__, __eq__, __str__) ensinam seus objetos a responder a +, == e print. Dinheiro devolvia None quando as moedas eram diferentes. Funcionou — mas a gente prometeu voltar nisso hoje. memória muscular
Quando as moedas batiam, somava. Quando não batiam, imprimia um aviso e devolvia None. Parecia razoável. Não era.
print avisa, mas quem chamou a soma não é obrigado a ler. O programa continua rodando com um None na mão.return None é um valor mágico: quem recebe precisa lembrar de checar se veio None. Esquecer é fácil.class Dinheiro:
def __init__(self, valor, moeda):
self.valor = valor
self.moeda = moeda
def __add__(self, outro):
if self.moeda != outro.moeda:
print(f"⚠️ Não dá pra somar {self.moeda} com {outro.moeda}")
return None # ← o defeito mora aqui
return Dinheiro(self.valor + outro.valor, self.moeda)
carteira = Dinheiro(10, "BRL")
total = carteira + Dinheiro(5, "USD") # imprime o aviso...
print(total) # None ← e segue como se nada fosse pergunta pra turma · 2 minutos
total que virou None não fica parado — ele é usado mais pra frente no programa. O que acontece quando alguém escreve print(total.valor) três telas depois, achando que total é um Dinheiro de verdade? a resposta · e por que ela importa
O None viaja silencioso pelo programa até alguém tentar usá-lo como se fosse um objeto. Aí, sim, trava — mas numa linha que não tem nada a ver com a soma.
AttributeError aponta pra linha do total.valor — não pra soma das moedas, que foi o verdadeiro problema. Debugar isso é caçar fantasma.carteira = Dinheiro(10, "BRL")
total = carteira + Dinheiro(5, "USD") # aqui imprimiu o aviso e devolveu None
# ... 50 linhas depois, em outra função, outro arquivo ...
print(total.valor)
# AttributeError: 'NoneType' object has no attribute 'valor' começando pelo problema
None, -1, False, ""): quem chama precisa adivinhar que aquele valor significa "deu errado" — e lembrar de checar toda vez. print("erro!")): o aviso some no meio de mil outras linhas do terminal, e o programa continua como se estivesse tudo bem. Como uma função pode parar tudo e gritar "deu errado!" de um jeito que quem chamou seja obrigado a lidar — ou o programa para na hora, no lugar certo?
o problema em código
Você já viu erros que param o programa na hora. Eles têm nome e sobrenome — e param exatamente na linha que causou o problema.
None silencioso.raise) e também capturá-los pra não deixar o programa morrer (try/except).print(10 / 0)
# ZeroDivisionError: division by zero
idade = int("abc")
# ValueError: invalid literal for int() with base 10: 'abc'
lista = [1, 2, 3]
print(lista[10])
# IndexError: list index out of range
pessoa = {"nome": "Ana"}
print(pessoa["idade"])
# KeyError: 'idade' a ideia da solução
raise): a função que percebe o problema só dispara o alarme — "saldo insuficiente!" — e para ali mesmo. Não precisa saber o que fazer depois. try/except): quem chamou a função decide a reação — mostrar mensagem pro usuário, tentar de novo, registrar num log. Cada contexto reage do seu jeito. Saímos de "a função tenta resolver tudo sozinha" para "a função avisa, quem chamou decide".
as peças que você vai encontrar
raise: dispara uma exceção de propósito. raise ValueError("mensagem") para o programa ali e sobe o erro pra quem chamou. try: marca um bloco "arriscado" — código que pode dar erro. Você diz ao Python "tenta isso aqui, e fica de olho". except: a rede de segurança. Se o bloco try levantar uma exceção, o except captura e roda um plano B em vez de deixar o programa morrer. Existem ainda else (roda se não deu erro) e finally (roda sempre). Vamos chegar nelas — comece pelo trio acima.
ferramenta 1 · capturar
Envolva o código arriscado num try. Se ele estourar, o except entra em ação — e o programa não morre.
42, o try roda inteiro e o except é ignorado."abc", o int() levanta ValueError, o try é interrompido na hora e o except assume — sem travar o programa.try:
idade = int(input("Digite sua idade: "))
print(f"Ano que vem você faz {idade + 1}")
except ValueError:
print("Isso não é um número válido!")
print("O programa continua rodando.") # roda mesmo se deu erro ferramenta 2 · disparar
Quando sua função percebe uma situação inválida, ela mesma levanta a exceção. Não devolve None — dispara o alarme.
raise interrompe a função na hora — as linhas depois dele não rodam. O erro sobe pra quem chamou sacar.return None e um print, agora a função se recusa a devolver um valor errado.def sacar(saldo, valor):
if valor > saldo:
raise ValueError("Saldo insuficiente para o saque")
return saldo - valor
print(sacar(100, 30)) # 70 ← deu certo
print(sacar(100, 150)) # para aqui:
# ValueError: Saldo insuficiente para o saque capturar com pontaria
Você pode ter vários except no mesmo try — cada um trata um tipo específico de exceção. O Python escolhe o que combina.
except mira um tipo. Capturar específico deixa claro qual problema você esperava e como reagir a cada um.except Exception: que captura quase tudo — mas use com parcimônia. Capturar específico é quase sempre melhor (mais sobre isso no fim da aula).def dividir(a, b):
return int(a) / int(b)
try:
resultado = dividir("10", "0")
except ValueError:
print("Digite apenas números!")
except ZeroDivisionError:
print("Não dá pra dividir por zero!")
# saída: Não dá pra dividir por zero! lendo o erro capturado
Com except ... as erro, você guarda a exceção numa variável e pode ler a mensagem que veio dentro dela.
erro é o objeto da exceção. Imprimi-lo mostra a mensagem que quem deu raise escreveu lá atrás.try:
numero = int("abc")
except ValueError as erro:
print(f"Deu ruim: {erro}")
# Deu ruim: invalid literal for int() with base 10: 'abc' as duas peças que faltavam
else roda só quando o try deu certo. finally roda sempre — deu certo ou não. Ideal pra limpeza (fechar arquivo, conexão).
else separa o "código que só faz sentido se deu certo" do bloco arriscado — deixa o try enxuto, só com a parte que pode falhar.finally é a faxina garantida: roda mesmo se um raise escapar. Perfeito pra liberar recursos (assunto que volta na aula 13, com arquivos).try:
numero = int("42")
except ValueError:
print("Não era número")
else:
print(f"Converteu certo: {numero}") # roda só se NÃO deu erro
finally:
print("Fim da tentativa") # roda SEMPRE
# saída:
# Converteu certo: 42
# Fim da tentativa agora podemos dar o nome
ValueError ou ZeroDivisionError. Carrega uma mensagem e pode carregar dados. Não use exceção pra controlar fluxo normal (um if resolve). Use pra sinalizar o que é excepcional — o que foge do esperado.
antes e depois · lado a lado
print some no meio do terminal analogia do cotidiano
raise é o disjuntor desarmando: na primeira sobrecarga, corta tudo. Melhor parar agora do que deixar a fiação esquentar em silêncio. try/except é você no quadro de luz: percebe que desarmou, entende qual circuito foi e decide o que fazer — religar, chamar o eletricista, isolar o problema. Engolir o erro (except: pass) é colar fita isolante no disjuntor pra ele parar de desarmar. O problema não sumiu — você só desligou o aviso.
toda exceção tem família
Exceções são classes — e classes têm herança (aulas 07-10). Toda exceção desce de Exception. Isso significa que capturar uma classe-mãe captura também as filhas.
ArithmeticError pega ZeroDivisionError também — porque é uma filha. Herança da aula 07, agora aplicada a erros.except Exception pega quase tudo — poderoso e perigoso ao mesmo tempo.# BaseException
# └── Exception ← capture a partir daqui
# ├── ArithmeticError
# │ └── ZeroDivisionError
# ├── LookupError
# │ ├── IndexError
# │ └── KeyError
# ├── ValueError
# └── TypeError
try:
resultado = 10 / 0
except ArithmeticError: # pai de ZeroDivisionError
print("Capturado pela classe-mãe!")
# saída: Capturado pela classe-mãe! panorama · as exceções nativas mais comuns
| item | detalhe |
|---|---|
ValueError | Tipo certo, valor errado. int("abc") — é string, mas não dá pra virar número. |
TypeError | Tipo errado pra operação. "texto" + 5 — não dá pra somar texto com número. |
ZeroDivisionError | Divisão (ou módulo) por zero. 10 / 0. |
IndexError | Índice fora do tamanho da lista. [1, 2, 3][10]. |
KeyError | Chave que não existe no dicionário. {"a": 1}["b"]. |
FileNotFoundError | Arquivo que não existe ao tentar abrir. Volta forte na aula 13. |
AttributeError | Atributo/método que não existe no objeto. None.valor — foi o erro do nosso Dinheiro. |
Exception | A classe-mãe de quase todas. É dela que herdam as suas exceções customizadas. |
parte 2 · seus próprios erros
SaldoInsuficienteError) conta a história sem precisar ler a mensagem. O tipo do erro já é a documentação. Exception. Toda a maquinaria de raise e except já funciona de graça — herança da aula 07. Como capturar exatamente o erro do meu sistema, sem confundir com um ValueError qualquer que veio de outro lugar?
a solução em código
Uma linha. Herda de Exception, ganha um nome que fala a língua do seu sistema, e já pode ser levantada e capturada.
except SaldoInsuficienteError diz exatamente o que está sendo tratado. Nenhum comentário necessário.except só pega saldo insuficiente — um ValueError vindo de outro ponto passa direto, como deveria.class SaldoInsuficienteError(Exception):
pass
def sacar(saldo, valor):
if valor > saldo:
raise SaldoInsuficienteError("Você não tem saldo suficiente")
return saldo - valor
try:
sacar(100, 150)
except SaldoInsuficienteError as erro:
print(f"Operação negada: {erro}")
# saída: Operação negada: Você não tem saldo suficiente exceção que carrega dados
Defina um __init__ (aula 02!) e a exceção carrega os dados do problema. Quem captura lê esses dados pra reagir com precisão.
saldo, valor e falta. Quem captura usa esses dados pra decidir o que mostrar.super().__init__(...) é o mesmo super() da aula 07 — aqui ele entrega a mensagem pra classe-mãe Exception guardar.class SaldoInsuficienteError(Exception):
def __init__(self, saldo, valor):
self.saldo = saldo
self.valor = valor
self.falta = valor - saldo
# super() monta a mensagem que aparece no print do erro
super().__init__(f"Faltam R$ {self.falta:.2f} para sacar R$ {valor:.2f}")
try:
raise SaldoInsuficienteError(saldo=100, valor=150)
except SaldoInsuficienteError as erro:
print(erro) # Faltam R$ 50.00 para sacar R$ 150.00
print(erro.falta) # 50 ← dá pra ler os dados do erro anatomia
try, except, else e finally têm uma ordem fixa e um papel cada. Decora essa sequência.
try — o código arriscadoAqui vai só o que pode falhar. Quanto menor o bloco, mais fácil saber o que deu errado.
except — o plano BUm ou vários, cada um pra um tipo de exceção. Roda só se o try levantar aquele erro.
else — deu tudo certoOpcional. Roda apenas quando o try terminou sem nenhuma exceção. Bom pra separar o "caminho feliz".
finally — aconteça o que acontecerOpcional. Roda sempre, no fim de tudo. É onde você libera recursos (fechar arquivo, conexão, etc.).
uso com critério
except: pass) é desligar o alarme de incêndio porque o barulho incomoda. O fogo continua — você só parou de ser avisado. except ValueError em vez de except Exception. Capturar largo demais esconde até erros de digitação (um NameError que deveria estourar). Se você captura um erro, faça algo útil: trate, registre em log, ou levante de novo (raise). Capturar pra ignorar é pior que não capturar.
armadilha · não faça isso
Os dois blocos abaixo "funcionam" — nenhum trava o programa. Mas ambos transformam um erro real num mistério insolúvel.
except: pass é o pior dos mundos: o erro acontece, é capturado e jogado fora. Zero pistas pra quem for debugar.except Exception largo engole o NameError do nome escrito errado — um bug que o Python queria ter te mostrado.# ❌ Engolir: o erro some, mas o problema continua
try:
numero = int("abc")
except:
pass # ninguém nunca vai saber o que deu errado
# ❌ Genérico demais: mascara até bug de digitação
try:
total = calcualr_preco(100) # 'calcualr' está escrito errado
except Exception:
total = 0 # esconde o NameError de propósito
# ✅ Específico e honesto
try:
numero = int("abc")
except ValueError as erro:
print(f"Entrada inválida: {erro}") por que isso importa
O programa sobrevive a entradas ruins e falhas pontuais sem morrer no primeiro tropeço.
A falha para na linha que a causou — não 50 linhas depois, disfarçada.
O usuário vê "saldo insuficiente", não um traceback assustador.
Exceções customizadas dão nome aos erros do seu sistema — viram documentação viva.
finally libera recursos mesmo quando algo dá errado no meio.
Quem usa sua função sabe quais erros ela pode levantar — e como tratá-los.
glossário rápido
| item | detalhe |
|---|---|
| Exceção | Objeto que representa um erro. Instância de uma classe como ValueError ou da sua própria. |
raise | Levanta (dispara) uma exceção de propósito. Interrompe a função e sobe o erro pra quem chamou. |
try | Marca o bloco de código arriscado que pode levantar uma exceção. |
except | Captura uma exceção levantada no try e executa o plano B. Pode mirar um tipo específico. |
else | Roda apenas se o try terminou sem nenhuma exceção. É o caminho feliz. |
finally | Roda sempre, com erro ou sem erro. Usado pra liberar recursos (limpeza). |
| Hierarquia de exceções | Exceções são classes com herança. Capturar a classe-mãe captura também as filhas. |
| Exceção customizada | Classe que herda de Exception pra representar um erro específico do seu domínio. |
| Engolir erro | Anti-padrão: capturar uma exceção e não fazer nada útil (except: pass). |
laboratório em sala · duplas
Em duplas. 30 minutos. Objetivo: uma ContaBancaria que se recusa a fazer operações inválidas — levantando exceções em vez de devolver valores mágicos.
SaldoInsuficienteError(Exception) com um __init__ que guarda saldo, valor e calcula quanto falta.
Com titular e saldo. O método depositar(valor) levanta ValueError se o valor não for positivo.
sacar(valor) levanta ValueError se o valor for inválido e SaldoInsuficienteError se faltar saldo.
Faça um saque válido e um inválido dentro de um try, com um except pra cada tipo de erro e um finally mostrando o saldo final.
resolução · passo 1
Exception e damos o nome do domínio: SaldoInsuficienteError. Esse nome vai aparecer no except e contar a história sozinho. __init__ guarda saldo e valor, calcula falta e monta a mensagem via super().__init__. A exceção é só uma classe comum — tudo o que você aprendeu sobre __init__ e super() (aulas 02 e 07) vale aqui.
resolução · passo 1
Ela guarda o contexto do erro: quanto tinha, quanto se tentou sacar e quanto faltou.
saldo, valor_saque, falta) ficam disponíveis pra quem capturar o erro.super().__init__ — é o que aparece quando você dá print(erro).class SaldoInsuficienteError(Exception):
def __init__(self, saldo, valor_saque):
self.saldo = saldo
self.valor_saque = valor_saque
self.falta = valor_saque - saldo
super().__init__(
f"Saldo de R$ {saldo:.2f} insuficiente para sacar "
f"R$ {valor_saque:.2f} (faltam R$ {self.falta:.2f})"
) resolução · passos 2 e 3
Cada método valida antes de agir. Valor inválido? ValueError. Saldo curto? SaldoInsuficienteError. Nenhuma operação errada passa.
raise: genérico (ValueError) pro valor inválido, específico (SaldoInsuficienteError) pro problema do domínio.class ContaBancaria:
def __init__(self, titular, saldo=0):
self.titular = titular
self.saldo = saldo
def depositar(self, valor):
if valor <= 0:
raise ValueError("O valor do depósito deve ser positivo")
self.saldo += valor
return self.saldo
def sacar(self, valor):
if valor <= 0:
raise ValueError("O valor do saque deve ser positivo")
if valor > self.saldo:
raise SaldoInsuficienteError(self.saldo, valor)
self.saldo -= valor
return self.saldo resolução · passo 4
Um saque válido, um que estoura. Cada erro tem seu except, e o finally mostra o saldo aconteça o que acontecer.
sacar levanta a exceção; a linha seguinte é pulada e o except certo assume.erro.falta lê o dado que guardamos na exceção — a mensagem pro usuário fica precisa, não genérica.from banco import ContaBancaria, SaldoInsuficienteError
conta = ContaBancaria("Ana", saldo=100)
try:
conta.sacar(30)
print(f"Saque OK. Saldo: R$ {conta.saldo:.2f}")
conta.sacar(500) # 💥 estoura aqui
print("Esta linha nunca roda")
except SaldoInsuficienteError as erro:
print(f"❌ {erro}")
print(f" Deposite mais R$ {erro.falta:.2f} para conseguir sacar.")
except ValueError as erro:
print(f"❌ Valor inválido: {erro}")
finally:
print(f"Saldo final: R$ {conta.saldo:.2f}") resolução · saída
O saque válido passa, o inválido é barrado com mensagem clara, e o saldo final aparece intacto — o saque que falhou não tirou nada.
finally rodou mesmo com a exceção no meio — por isso o saldo final aparece sempre.$ python main.py
Saque OK. Saldo: R$ 70.00
❌ Saldo de R$ 70.00 insuficiente para sacar R$ 500.00 (faltam R$ 430.00)
Deposite mais R$ 430.00 para conseguir sacar.
Saldo final: R$ 70.00 encerramento
None ou imprimir aviso deixa o erro escapar e explodir longe da causa. raise dispara o erro na origem; try/except captura e trata onde faz sentido. try (arriscado), except (plano B), else (deu certo), finally (sempre). Exception pra criar exceções com nome e dados do seu domínio. exercícios · visão geral
Sete exercícios em quatro níveis. Faça pelo menos um de cada nível antes da próxima aula.
1 exercício — Conversor seguro. Foco: try/except com erros nativos.
2 exercícios — Validador de idade e Estoque. Foco: raise e primeira exceção customizada.
2 exercícios — Dinheiro 2.0 e Validador de cadastro. Foco: refatorar com exceções e carregar dados no erro.
1 exercício — Hierarquia de erros bancários. Foco: criar uma família de exceções customizadas.
exercícios · nível fácil
Função ler_inteiro(texto) que pede um número ao usuário e usa try/except ValueError pra repetir o pedido até receber um número válido. Foco: capturar ValueError de int() num laço.
exercícios · nível médio
Função validar_idade(idade) que levanta ValueError com mensagem clara se a idade for negativa ou maior que 150. Teste chamando dentro de um try/except. Foco: usar raise pra barrar entrada inválida.
Classe Estoque com uma lista de itens e o método remover(). Crie a exceção EstoqueVazioError e levante-a quando tentarem remover de um estoque vazio. Foco: primeira exceção customizada.
exercícios · nível difícil
Refatore a classe Dinheiro da aula 11: em vez de imprimir aviso e devolver None quando as moedas diferem, crie MoedaIncompativelError e levante-a em __add__ e __sub__. Foco: trocar valor mágico por exceção — a promessa da aula 11.
Crie CadastroInvalidoError que carrega o campo com problema. A função validar_cadastro(nome, email, idade) levanta a exceção indicando qual campo falhou (nome vazio, e-mail sem @, idade fora da faixa). Foco: exceção que carrega dados do erro.
exercícios · nível avançado
Um exercício só, que usa hierarquia de exceções e captura por classe-mãe.
Crie ContaError(Exception) como base, e duas filhas: SaldoInsuficienteError e ValorInvalidoError. Refatore a ContaBancaria do laboratório pra levantar as filhas. No main, mostre que um único except ContaError captura as duas — porque ambas herdam da base. Foco: herança aplicada a exceções (a hierarquia da aula).
Hoje garantimos que o programa não morre no primeiro tropeço. Na próxima aula, aprendemos a fazer ele lembrar das coisas: ler e escrever arquivos, salvar objetos em JSON — e o finally de hoje vira o with que fecha tudo sozinho.