Python raise
Até aqui, quem criava os erros era o Python: o int("dez") dava ValueError, o 10 / 0 dava ZeroDivisionError. Você só lia os erros e os tratava. Mas o seu próprio código também sabe quando algo está errado. Uma nota 15 numa escala de 0 a 10, um saque maior que o saldo, um desconto de 150%: o Python não acha nada de errado nesses números, mas você acha. Para avisar, existe o raise (em inglês, "levantar"): você lança um erro, exatamente como o Python lança os dele.
- A lançar um erro com
raise ValueError("mensagem")e a escrever uma boa mensagem. - Por que lançar um erro é melhor que dar um
printe seguir em frente. - Que quem chama a sua função trata o seu erro com o mesmo
try/exceptde sempre. - Qual tipo de erro escolher:
ValueError,TypeErrore outros. - O
raisesozinho, que relança o erro, e oraise … from. - Os erros reais de quem está começando com o
raise.
Todos os programas desta aula rodaram no Python 3.14 da máquina da Ana (usuária ana, computador notebook) com python3 arquivo.py. Para entender bem, vale ter visto Funções, return e try e except.
A ideia: levantar a mão
A Ana trabalha no caixa da padaria. Um cliente pede para pagar R$ 50 com uma nota de R$ 3. Não existe nota de 3 reais. O que ela faz?
- Jeito errado: resmunga "nota estranha" baixinho e registra a venda mesmo assim. No fim do dia, o caixa não fecha, e ninguém sabe por quê.
- Jeito certo: para tudo, levanta a mão e chama a gerente: "Problema aqui: nota de 3 reais não existe". A gerente decide o que fazer.
O raise é o "levantar a mão". Uma função que recebeu algo impossível não deve tentar seguir em frente nem só resmungar com um print: ela para e lança um erro com uma mensagem clara. Quem chamou a função (a "gerente") decide o que fazer: tratar com try/except ou deixar o programa parar.
1. O primeiro raise
A professora da Ana usa uma função para registrar as notas, que vão de 0 a 10:
def registrar_nota(nota):
if nota < 0 or nota > 10:
raise ValueError(f"A nota deve ficar entre 0 e 10, mas veio {nota}.")
print(f"Nota {nota} registrada.")
registrar_nota(8)
registrar_nota(15)
print("Fim")
- if nota < 0 or nota > 10:
- A conferência: a nota está fora da escala? (veja and, or e not).
- raise
- "Lance": a palavra-chave que lança um erro. Daqui o Python não segue para a linha de baixo; é como um erro que ele mesmo tivesse encontrado.
- ValueError
- O tipo do erro.
ValueError("erro de valor") é o certo para "o tipo está certo (é um número), mas o valor não serve". É o mesmo que oint("dez")lança. - ( f"A nota deve…" )
- A mensagem, entre parênteses, como se você chamasse uma função. Ela vai aparecer na última linha do traceback. A f-string coloca o valor que chegou na mensagem.
- print(f"Nota {nota} registrada.")
- Só chega aqui se o
raisenão rodou, ou seja, se a nota é válida. Não precisa deelse: oraisejá interrompe.
$ python3 nota.py
Nota 8 registrada.
Traceback (most recent call last):
File "/home/ana/nota.py", line 8, in <module>
registrar_nota(15)
~~~~~~~~~~~~~~^^^^
File "/home/ana/nota.py", line 3, in registrar_nota
raise ValueError(f"A nota deve ficar entre 0 e 10, mas veio {nota}.")
ValueError: A nota deve ficar entre 0 e 10, mas veio 15.
Um traceback igualzinho aos do Python. Leia de baixo para cima, como na aula Lendo uma Mensagem de Erro:
- A última linha tem o tipo que você escolheu (
ValueError) e a sua mensagem, com o valor que chegou: 15. - O
Filede baixo aponta a linha 3, a doraise: foi ali que o erro foi lançado. - O
Filede cima mostra quem chamou com o valor ruim: a linha 8,registrar_nota(15). É lá que está o defeito de verdade.
O Fim não apareceu: um erro lançado com raise e não tratado para o programa, como qualquer outro.
Uma boa mensagem
A mensagem é para uma pessoa ler, talvez você mesmo daqui a seis meses. Compare:
| Mensagem | Ajuda? |
|---|---|
raise ValueError("Erro") | Quase nada: que erro? |
raise ValueError("Nota inválida") | Melhor: diz o que estava errado |
raise ValueError(f"A nota deve ficar entre 0 e 10, mas veio {nota}.") | Ótima: diz a regra e o valor que chegou |
Missão: a função sacar deixa o saldo ficar negativo (rode e veja o -130.00). Faça ela lançar um ValueError com uma mensagem que comece com Saldo insuficiente quando o valor for maior que o saldo. O programa vai parar no segundo saque, e é isso mesmo que se quer.
def sacar(saldo, valor):
return saldo - valor
saldo = 100.0
saldo = sacar(saldo, 30)
print(f"Saldo: R$ {saldo:.2f}")
saldo = sacar(saldo, 200)
print(f"Saldo: R$ {saldo:.2f}")
def sacar(saldo, valor):
if valor > saldo:
raise ValueError(f"Saldo insuficiente: você tem R$ {saldo:.2f}.")
return saldo - valor
saldo = 100.0
saldo = sacar(saldo, 30)
print(f"Saldo: R$ {saldo:.2f}")
saldo = sacar(saldo, 200)
print(f"Saldo: R$ {saldo:.2f}")
Antes e depois, no terminal da Ana:
$ python3 sacar_ini.py
Saldo: R$ 70.00
Saldo: R$ -130.00
$ python3 sacar_sol.py
Saldo: R$ 70.00
Traceback (most recent call last):
File "/home/ana/sacar_sol.py", line 10, in <module>
saldo = sacar(saldo, 200)
File "/home/ana/sacar_sol.py", line 3, in sacar
raise ValueError(f"Saldo insuficiente: você tem R$ {saldo:.2f}.")
ValueError: Saldo insuficiente: você tem R$ 70.00.
2. Por que não um print?
Parece mais simples avisar com um print e sair da função com return. Olhe o que acontece:
def validar_nota(nota):
if nota < 0 or nota > 10:
print("Nota inválida!")
return
return nota
notas = [validar_nota(8), validar_nota(15), validar_nota(9)]
print(notas)
print(f"Soma: {sum(notas)}")
$ python3 print_none.py
Nota inválida!
[8, None, 9]
Traceback (most recent call last):
File "/home/ana/print_none.py", line 10, in <module>
print(f"Soma: {sum(notas)}")
~~~^^^^^^^
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
O aviso apareceu, mas o programa continuou. O return sozinho devolveu None (o "nada" do Python; veja O Valor None), e esse None entrou na lista no lugar da nota. Duas linhas depois, a soma tropeçou nele com um TypeError que fala de 'NoneType' e não diz nada sobre notas. Num programa maior, esse None poderia viajar muito mais longe antes de causar problema, e o traceback apontaria para um lugar que não tem culpa nenhuma.
Com raise, o programa para no lugar certo, com a mensagem certa:
def validar_nota(nota):
if nota < 0 or nota > 10:
raise ValueError(f"A nota deve ficar entre 0 e 10, mas veio {nota}.")
return nota
notas = [validar_nota(8), validar_nota(15), validar_nota(9)]
print(notas)
print(f"Soma: {sum(notas)}")
$ python3 raise_none.py
Traceback (most recent call last):
File "/home/ana/raise_none.py", line 7, in <module>
notas = [validar_nota(8), validar_nota(15), validar_nota(9)]
~~~~~~~~~~~~^^^^
File "/home/ana/raise_none.py", line 3, in validar_nota
raise ValueError(f"A nota deve ficar entre 0 e 10, mas veio {nota}.")
ValueError: A nota deve ficar entre 0 e 10, mas veio 15.
As marcas apontam exatamente o culpado: validar_nota(15), na linha 7.
print + return | raise | |
|---|---|---|
| O programa continua? | Sim, com um None no lugar do valor | Não, a não ser que alguém trate com try |
| Onde o problema aparece | Depois, longe, com outra mensagem | Na hora, na linha certa |
| Quem chamou pode reagir? | Só testando se veio None | Sim, com try/except |
| A mensagem vai para | A tela, sempre (mesmo que ninguém queira) | O traceback, ou o except que decidir mostrá-la |
3. Quem chama decide: try e except
O erro que você lança é um erro como outro qualquer. Quem chama a função pode tratá-lo com o try/except da aula try e except. Aqui, o programa pede a nota de novo até vir uma válida:
def validar_nota(nota):
if nota < 0 or nota > 10:
raise ValueError(f"A nota deve ficar entre 0 e 10, mas veio {nota}.")
return nota
while True:
try:
nota = validar_nota(int(input("Nota: ")))
break
except ValueError as e:
print(f"Erro: {e}")
print(f"Nota registrada: {nota}")
$ python3 pegar.py Nota: 15 Erro: A nota deve ficar entre 0 e 10, mas veio 15. Nota: dez Erro: invalid literal for int() with base 10: 'dez' Nota: -1 Erro: A nota deve ficar entre 0 e 10, mas veio -1. Nota: 8 Nota registrada: 8
Repare: o mesmo except ValueError pegou dois erros diferentes. O dez deu o ValueError do int(), com a mensagem do Python; o 15 e o -1 deram o ValueError da sua função, com a sua mensagem. Para quem trata, tanto faz quem lançou: o que importa é o tipo. É por isso que escolher o tipo certo é importante.
A função validar_nota não sabe o que fazer com uma nota ruim: num programa, o certo é perguntar de novo; noutro, pular o aluno; noutro, parar tudo. Por isso ela só lança o erro. Quem sabe o que fazer é quem chamou. Essa separação ("quem descobre o problema avisa; quem sabe resolver, resolve") é uma das ideias mais úteis da programação.
4. Qual tipo de erro lançar
Você pode lançar qualquer tipo de erro do Python, mas escolha o que descreve o problema, porque é pelo tipo que o except de quem chama vai reconhecê-lo. Os mais usados:
| Tipo | Quando lançar | Exemplo |
|---|---|---|
ValueError | O tipo está certo, o valor não serve | Nota 15, desconto de 150%, quantidade negativa |
TypeError | Veio um tipo que a função não aceita | Um texto "50" onde devia vir um número |
KeyError / IndexError | Procurou algo que não existe numa coleção | Raramente lançados à mão: o dicionário e a lista já lançam sozinhos |
FileNotFoundError | Um arquivo necessário não existe | Também costuma vir sozinho, do open() |
| Um erro seu | Nenhum dos de cima descreve bem o problema | SaldoInsuficiente: aula Criando suas Exceções |
Um exemplo com os dois primeiros. O isinstance(preco, (int, float)) pergunta "o preco é um int ou um float?" e responde True ou False:
def aplicar_desconto(preco, porcentagem):
if not isinstance(preco, (int, float)):
raise TypeError(f"O preço tem de ser número, não {type(preco).__name__}.")
if porcentagem < 0 or porcentagem > 100:
raise ValueError(f"Desconto de {porcentagem}% não existe.")
return preco * (1 - porcentagem / 100)
print(aplicar_desconto(50.0, 10))
print(aplicar_desconto("50", 10))
$ python3 tipo.py
45.0
Traceback (most recent call last):
File "/home/ana/tipo.py", line 10, in <module>
print(aplicar_desconto("50", 10))
~~~~~~~~~~~~~~~~^^^^^^^^^^
File "/home/ana/tipo.py", line 3, in aplicar_desconto
raise TypeError(f"O preço tem de ser número, não {type(preco).__name__}.")
TypeError: O preço tem de ser número, não str.
E com um desconto impossível, a outra conferência:
def aplicar_desconto(preco, porcentagem):
if porcentagem < 0 or porcentagem > 100:
raise ValueError(f"Desconto de {porcentagem}% não existe.")
return preco * (1 - porcentagem / 100)
print(aplicar_desconto(50.0, 150))
$ python3 desconto_valor.py
Traceback (most recent call last):
File "/home/ana/desconto_valor.py", line 7, in <module>
print(aplicar_desconto(50.0, 150))
~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/home/ana/desconto_valor.py", line 3, in aplicar_desconto
raise ValueError(f"Desconto de {porcentagem}% não existe.")
ValueError: Desconto de 150% não existe.
Sem essa conferência, aplicar_desconto(50.0, 150) devolveria -25.0: a padaria pagaria R$ 25 para o cliente levar o produto.
Você pode criar os seus próprios tipos de erro, como SaldoInsuficiente ou NotaInvalida. Isso pede classes, que são do Nível 3; está na aula Criando suas Exceções. Até lá, ValueError resolve a grande maioria dos casos.
5. raise sozinho: relançar o erro
Às vezes você quer fazer alguma coisa quando o erro acontece (anotar, avisar) e, mesmo assim, deixar o erro seguir em frente, porque não é você quem sabe resolvê-lo. Dentro de um except, um raise sem nada depois relança o mesmo erro que acabou de ser pego:
def ler_preco(texto):
try:
return float(texto)
except ValueError:
print(f"[registro] preço mal digitado: {texto}")
raise
print(ler_preco("4.50"))
print(ler_preco("4,50"))
$ python3 reraise.py
4.5
[registro] preço mal digitado: 4,50
Traceback (most recent call last):
File "/home/ana/reraise.py", line 10, in <module>
print(ler_preco("4,50"))
~~~~~~~~~^^^^^^^^
File "/home/ana/reraise.py", line 3, in ler_preco
return float(texto)
ValueError: could not convert string to float: '4,50'
- except ValueError:
- Pega o erro do
float(), que não entende a vírgula de4,50. - print(f"[registro] …")
- Faz a anotação.
- raise
- Sozinho, sem tipo: "lance de novo o erro que eu peguei". O erro continua sua viagem, como se o
exceptnão o tivesse segurado.
O traceback é o original: o erro aponta a linha 3, a do float(texto), e a mensagem é a do Python. Nada se perdeu; só se acrescentou o registro antes.
raise … from: trocar o erro por outro mais claro
Outro caso: o erro que aconteceu "por dentro" não faz sentido para quem usa a sua função. Um KeyError: 'café' diz pouco; "produto não cadastrado" diz tudo. Você pode lançar um erro novo e dizer que ele veio do antigo, com from ("de"):
precos = {"pão": 0.75, "leite": 5.49}
def preco_de(produto):
try:
return precos[produto]
except KeyError as e:
raise ValueError(f"Produto não cadastrado: {produto}") from e
print(preco_de("café"))
$ python3 from.py
Traceback (most recent call last):
File "/home/ana/from.py", line 6, in preco_de
return precos[produto]
~~~~~~^^^^^^^^^
KeyError: 'café'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/ana/from.py", line 11, in <module>
print(preco_de("café"))
~~~~~~~~^^^^^^^^
File "/home/ana/from.py", line 8, in preco_de
raise ValueError(f"Produto não cadastrado: {produto}") from e
ValueError: Produto não cadastrado: café
Dois tracebacks, ligados pela frase do meio: "A exceção acima foi a causa direta da exceção a seguir." Embaixo, o erro novo (ValueError, com a sua mensagem); em cima, o erro original, guardado para quem quiser investigar. Compare com o "During handling of the above exception, another exception occurred" da aula try e except: aquele era um erro acidental dentro do except; este é uma troca de propósito.
Formas diferentes de fazer a mesma coisa
| Escrita | O que faz | Quando usar |
|---|---|---|
raise ValueError("mensagem") | Lança um erro novo, com mensagem | O normal. Sempre com mensagem |
raise ValueError | Lança um erro sem mensagem | Evite: a última linha do traceback fica só ValueError |
raise (dentro de um except) | Relança o erro que foi pego, sem mudar nada | Quando você só quer anotar ou arrumar algo e deixar o erro seguir |
raise Novo("…") from e | Troca o erro por outro, guardando o original | Quando o erro de dentro não faz sentido para quem usa a função |
print(…) + return | Avisa e devolve None | Não use para erros: o programa segue com um valor errado |
O raise ValueError sem mensagem, de verdade:
estoque = 0
if estoque == 0:
raise ValueError
$ python3 sem_msg.py
Traceback (most recent call last):
File "/home/ana/sem_msg.py", line 3, in <module>
raise ValueError
ValueError
Funciona, mas quem lê só a última linha fica sem saber o que aconteceu.
Quando dá errado
idade = -3
if idade < 0:
raise "Idade negativa!"
$ python3 raise_texto.py
Traceback (most recent call last):
File "/home/ana/raise_texto.py", line 3, in <module>
raise "Idade negativa!"
TypeError: exceptions must derive from BaseException
"Exceções precisam derivar de BaseException." O raise só aceita erros (ValueError, TypeError…), e um texto não é um erro. Todos os tipos de erro do Python descendem de um "avô" comum chamado BaseException; é isso que a mensagem quer dizer. Ironia: tentando lançar o seu erro, você ganhou um TypeError. Como consertar: o texto vai dentro de um tipo de erro: raise ValueError("Idade negativa!").
idade = -3
if idade < 0:
raise ValueError, "Idade negativa!"
$ python3 raise_virgula.py
File "/home/ana/raise_virgula.py", line 3
raise ValueError, "Idade negativa!"
^
SyntaxError: invalid syntax
Esse jeito de escrever era do Python 2, que deixou de ser mantido em 2020; ainda aparece em tutoriais antigos na internet. No Python 3, a mensagem vai entre parênteses. Como consertar: raise ValueError("Idade negativa!").
raise sozinho fora de um except
idade = -3
if idade < 0:
raise
$ python3 raise_sozinho.py
Traceback (most recent call last):
File "/home/ana/raise_sozinho.py", line 3, in <module>
raise
RuntimeError: No active exception to reraise
"Nenhuma exceção ativa para relançar." O raise sozinho quer dizer "relance o erro que eu peguei", e aqui não havia erro nenhum: ele só funciona dentro de um except. O Python reclamou com outro tipo, o RuntimeError ("erro durante a execução"). Como consertar: diga qual erro lançar: raise ValueError("Idade negativa!").
rise no lugar de raise 3.14
idade = -3
if idade < 0:
rise ValueError("Idade negativa!")
$ python3 rise.py
File "/home/ana/rise.py", line 3
rise ValueError("Idade negativa!")
^^^^
SyntaxError: invalid syntax. Did you mean 'raise'?
O Python 3.14 reconhece a palavra-chave digitada errado e sugere raise (num 3.13, sai só invalid syntax). Como é SyntaxError, nada rodou.
Um raise dentro de um try com except ValueError logo embaixo, na mesma função, é pego por ela mesma: o erro não chega a quem chamou. Em geral isso é sinal de confusão; se a própria função sabe resolver, ela não precisa lançar nada, basta um if. Lance na função que descobre o problema e trate em quem chama, como na seção 3.
Referência rápida
| Escrita | O que faz |
|---|---|
raise ValueError("…") | Lança um erro de valor com a sua mensagem |
raise TypeError("…") | Lança um erro de tipo |
raise (num except) | Relança o erro que foi pego |
raise Novo("…") from e | Lança outro erro, guardando e como causa |
try/except ValueError as e | Trata o seu erro (e os do Python) em quem chama |
| Sintoma | Causa provável |
|---|---|
TypeError: exceptions must derive from BaseException | raise "texto": falta o tipo do erro |
SyntaxError: invalid syntax na vírgula | raise Tipo, "msg" do Python 2 |
RuntimeError: No active exception to reraise | raise sozinho fora de um except |
Did you mean 'raise'? | Palavra-chave digitada errado |
The above exception was the direct cause… | Não é um defeito: é um raise … from; leia os dois |
Um TypeError com 'NoneType' longe do problema | Uma função que deu print + return em vez de raise |
Exercícios
1. Complete o raise
Complete para lançar um erro de valor quando a quantidade for negativa:
2. A última linha
Um programa roda raise ValueError("Mês 13 não existe.") e ninguém trata. Escreva a última linha do traceback:
3. Por que uma função deve lançar um erro em vez de dar print("inválido") e return?
4. Uma função recebe a idade -5. Qual tipo de erro descreve melhor o problema?
5. O que faz um raise sozinho, sem nada depois, dentro de um except?
Missão final: a função lança, quem chama trata
Missão: a caixa vai "digitar" 15, -1 e 8. O laço de baixo já está pronto para tratar um ValueError, mas a função validar_nota aceita qualquer coisa: por isso o 15 passa. Faça a função lançar ValueError(f"A nota deve ficar entre 0 e 10, mas veio {nota}.") quando a nota estiver fora da escala, para o programa só aceitar o 8.
def validar_nota(nota):
return nota
while True:
try:
nota = validar_nota(int(input("Nota: ")))
break
except ValueError as e:
print(f"Erro: {e}")
print(f"Nota registrada: {nota}")
def validar_nota(nota):
if nota < 0 or nota > 10:
raise ValueError(f"A nota deve ficar entre 0 e 10, mas veio {nota}.")
return nota
while True:
try:
nota = validar_nota(int(input("Nota: ")))
break
except ValueError as e:
print(f"Erro: {e}")
print(f"Nota registrada: {nota}")
Resumo
raise Tipo("mensagem")lança um erro, como o Python lança os dele: o programa para ali, a não ser que alguém trate.- Escreva mensagens que digam a regra e o valor que chegou:
f"A nota deve ficar entre 0 e 10, mas veio {nota}.". - Lance em vez de dar
print+return: assim o problema aparece na hora e no lugar certo, e não umNoneviajando pelo programa. - Quem chama trata com
try/except; para oexcept, tanto faz se o erro veio do Python ou de você: vale o tipo. ValueErrorpara valor que não serve;TypeErrorpara tipo errado. Tipos próprios: Criando suas Exceções.raisesozinho, dentro de umexcept, relança o erro;raise Novo(…) from etroca por outro, guardando a causa.