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.

Você vai aprender
  • 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 print e seguir em frente.
  • Que quem chama a sua função trata o seu erro com o mesmo try/except de sempre.
  • Qual tipo de erro escolher: ValueError, TypeError e outros.
  • O raise sozinho, que relança o erro, e o raise … 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 o int("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 raise não rodou, ou seja, se a nota é válida. Não precisa de else: o raise já 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:

  1. A última linha tem o tipo que você escolheu (ValueError) e a sua mensagem, com o valor que chegou: 15.
  2. O File de baixo aponta a linha 3, a do raise: foi ali que o erro foi lançado.
  3. O File de 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:

MensagemAjuda?
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 + returnraise
O programa continua?Sim, com um None no lugar do valorNão, a não ser que alguém trate com try
Onde o problema apareceDepois, longe, com outra mensagemNa hora, na linha certa
Quem chamou pode reagir?Só testando se veio NoneSim, com try/except
A mensagem vai paraA 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.

Divisão de trabalho

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:

TipoQuando lançarExemplo
ValueErrorO tipo está certo, o valor não serveNota 15, desconto de 150%, quantidade negativa
TypeErrorVeio um tipo que a função não aceitaUm texto "50" onde devia vir um número
KeyError / IndexErrorProcurou algo que não existe numa coleçãoRaramente lançados à mão: o dicionário e a lista já lançam sozinhos
FileNotFoundErrorUm arquivo necessário não existeTambém costuma vir sozinho, do open()
Um erro seuNenhum dos de cima descreve bem o problemaSaldoInsuficiente: 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.

E se nenhum tipo servir?

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 de 4,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 except nã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

EscritaO que fazQuando usar
raise ValueError("mensagem")Lança um erro novo, com mensagemO normal. Sempre com mensagem
raise ValueErrorLança um erro sem mensagemEvite: a última linha do traceback fica só ValueError
raise (dentro de um except)Relança o erro que foi pego, sem mudar nadaQuando você só quer anotar ou arrumar algo e deixar o erro seguir
raise Novo("…") from eTroca o erro por outro, guardando o originalQuando o erro de dentro não faz sentido para quem usa a função
print(…) + returnAvisa e devolve NoneNã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

Lançar um texto
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!").

Vírgula no lugar dos parênteses
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.

Lançar o erro e tratar na mesma função

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

EscritaO 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 eLança outro erro, guardando e como causa
try/except ValueError as eTrata o seu erro (e os do Python) em quem chama
SintomaCausa provável
TypeError: exceptions must derive from BaseExceptionraise "texto": falta o tipo do erro
SyntaxError: invalid syntax na vírgularaise Tipo, "msg" do Python 2
RuntimeError: No active exception to reraiseraise 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 problemaUma 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:

if quantidade < 0: ("A quantidade não pode ser 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 um None viajando pelo programa.
  • Quem chama trata com try/except; para o except, tanto faz se o erro veio do Python ou de você: vale o tipo.
  • ValueError para valor que não serve; TypeError para tipo errado. Tipos próprios: Criando suas Exceções.
  • raise sozinho, dentro de um except, relança o erro; raise Novo(…) from e troca por outro, guardando a causa.