25. Lidando com Exceções com Elegância
No Capítulo 24, aprendemos como ler e entender exceções quando elas ocorrem. Agora vamos aprender como tratar exceções com elegância, permitindo que nossos programas se recuperem de erros em vez de travar. Isso é essencial para escrever programas robustos e amigáveis, que conseguem lidar com situações inesperadas.
Quando uma exceção ocorre em Python, o fluxo normal do programa para imediatamente. Mas e se a gente pudesse capturar essa exceção antes que ela derrube nosso programa? E se a gente pudesse responder ao erro, talvez pedindo para o usuário tentar de novo, ou usando um valor padrão, ou registrando o problema e continuando? É exatamente isso que o tratamento de exceções nos permite fazer.
25.1) Usando Blocos try e except
25.1.1) A Estrutura Básica de try e except
Um bloco try-except (try-except block) é a forma do Python dizer: "tente fazer isto e, se uma exceção acontecer, faça isto no lugar." A estrutura básica fica assim:
try:
# Código que pode levantar uma exceção
risky_operation()
except:
# Código que roda se QUALQUER exceção ocorrer
print("Something went wrong!")O bloco try contém código que pode levantar uma exceção. Se uma exceção ocorrer em qualquer lugar dentro do bloco try, o Python para imediatamente de executar o bloco try e pula para o bloco except. Se nenhuma exceção ocorrer, o bloco except é totalmente ignorado.
Vamos ver um exemplo concreto. Lembre do Capítulo 24 que tentar converter uma string inválida para um inteiro levanta um ValueError:
# Sem tratamento de exceção - o programa trava
user_input = "hello"
number = int(user_input) # ValueError: invalid literal for int() with base 10: 'hello'
print("This line never executes")Agora vamos tratar essa exceção com elegância:
# Com tratamento de exceção - o programa continua
user_input = "hello"
try:
number = int(user_input)
print(f"You entered: {number}")
except:
print("That's not a valid number!")
number = 0 # Usar um valor padrão
print(f"Using number: {number}")Output:
That's not a valid number!
Using number: 0O programa não travou! Quando int(user_input) levantou um ValueError, o Python pulou para o bloco except, imprimiu nossa mensagem de erro, definiu um valor padrão e então continuou com o resto do programa.
Aqui está o que acontece passo a passo:
Entendendo o "Pulo" - O Que Realmente Acontece
Quando dizemos que o Python "pula" para o bloco except, queremos dizer que ele abandona a execução sequencial normal. Essa é uma mudança fundamental de como seu programa flui — não é só um desvio simples como um if. Vamos ver isso em detalhe com um exemplo concreto:
# Observando o fluxo de execução com exceções
print("1. Starting program")
try:
print("2. Entered try block")
number = int("hello") # A exceção acontece AQUI
print("3. After conversion") # Esta linha NUNCA executa
result = number * 2 # Esta linha NUNCA executa
print("4. After calculation") # Esta linha NUNCA executa
except ValueError:
print("5. In except block - handling the error")
print("6. After try-except block")Output:
1. Starting program
2. Entered try block
5. In except block - handling the error
6. After try-except blockRepare que as linhas 3 e 4 nunca executam! No momento em que int("hello") levanta um ValueError, o Python:
- Para de executar o bloco try imediatamente — exatamente na linha onde a exceção ocorreu
- Procura uma cláusula except correspondente que consiga tratar esse tipo de exceção
- Pula para esse bloco except, ignorando todo o código restante no bloco try
- Continua após a estrutura try-except quando o bloco except termina
Isso é fundamentalmente diferente do fluxo normal do programa. Na execução normal, o Python roda cada linha de forma sequencial. Com uma exceção, o Python abandona o caminho atual e pega uma rota completamente diferente. Sem tratamento de exceção, o programa travaria na linha 2 e terminaria. Com tratamento de exceção, o programa se recupera e continua.
Por que isso importa:
Entender esse comportamento de "pulo" é crucial porque:
- Qualquer código depois da exceção no bloco try é ignorado — você não pode assumir que as linhas seguintes do try foram executadas
- Variáveis podem não ser inicializadas se a exceção acontecer antes da atribuição delas
- Você precisa planejar em que estado seu programa está quando o bloco except roda
25.1.2) Tratando Entrada do Usuário com Segurança
Um dos usos mais comuns do tratamento de exceções é validar entrada do usuário. Usuários podem digitar qualquer coisa, e a gente precisa lidar com entradas inválidas com elegância. Aqui vai um exemplo prático de um programa que pede a idade de um usuário:
# Entrada segura de idade com tratamento de exceção
print("Please enter your age:")
user_input = input()
try:
age = int(user_input)
print(f"You are {age} years old.")
# Calcular ano de nascimento (assumindo que o ano atual é 2024)
birth_year = 2024 - age
print(f"You were born around {birth_year}.")
except:
print("Invalid input! Age must be a number.")
print("Using default age of 0.")
age = 0Se o usuário digitar "25", a saída é:
Please enter your age:
25
You are 25 years old.
You were born around 1999.Se o usuário digitar "twenty-five", a saída é:
Please enter your age:
twenty-five
Invalid input! Age must be a number.
Using default age of 0.Repare como o programa trata o erro com elegância em vez de travar com um traceback. Isso é muito melhor para a experiência do usuário.
25.1.3) Tratando Múltiplas Operações em um Bloco try
Você pode colocar múltiplas operações em um único bloco try. Se qualquer uma delas levantar uma exceção, o Python pula para o bloco except imediatamente. Vamos começar com um exemplo simples:
# Duas operações no bloco try
print("Enter a number:")
user_input = input()
try:
number = int(user_input) # Primeira operação - pode levantar ValueError
result = 100 / number # Segunda operação - pode levantar ZeroDivisionError
print(f"100 / {number} = {result}")
except:
print("Something went wrong!")Se o usuário digitar "hello", a exceção acontece na primeira operação (conversão). Se o usuário digitar "0", a exceção acontece na segunda operação (divisão). De qualquer forma, nosso único bloco except captura.
Agora vamos estender isso para três operações:
# Múltiplas operações no bloco try
print("Enter two numbers to divide:")
numerator_input = input("Numerator: ")
denominator_input = input("Denominator: ")
try:
numerator = int(numerator_input) # Pode levantar ValueError
denominator = int(denominator_input) # Pode levantar ValueError
result = numerator / denominator # Pode levantar ZeroDivisionError
print(f"{numerator} / {denominator} = {result}")
except:
print("Something went wrong with the calculation!")
print("Make sure you enter valid numbers and don't divide by zero.")Se o usuário digitar "10" e "2":
Enter two numbers to divide:
Numerator: 10
Denominator: 2
10 / 2 = 5.0Se o usuário digitar "10" e "zero":
Enter two numbers to divide:
Numerator: 10
Denominator: zero
Something went wrong with the calculation!
Make sure you enter valid numbers and don't divide by zero.Se o usuário digitar "10" e "0":
Enter two numbers to divide:
Numerator: 10
Denominator: 0
Something went wrong with the calculation!
Make sure you enter valid numbers and don't divide by zero.Neste exemplo, três coisas diferentes podem dar errado: a conversão do numerador pode falhar, a conversão do denominador pode falhar, ou a divisão pode falhar (se o denominador for zero). Nosso único bloco except captura todos esses casos. Porém, essa abordagem tem uma limitação: não dá para saber qual erro específico aconteceu. Vamos resolver isso na próxima seção.
25.1.4) O Problema com Cláusulas except Nuas
Usar except: sem especificar um tipo de exceção é chamado de cláusula except nua (bare except clause). Embora ela capture todas as exceções, isso muitas vezes é amplo demais e pode esconder problemas inesperados. Considere este exemplo:
# Except nu captura TUDO - até coisas que a gente não espera
numbers = [10, 20, 30]
try:
index = 5 # A gente espera IndexError se index estiver fora do intervalo
value = numbers[index]
print(f"Value at index {index}: {value}")
except:
print("Could not access the list element.")Isso parece razoável — estamos tentando acessar um elemento da lista que talvez não exista. Mas e se tiver um erro de digitação no nosso código?
# E se tiver um erro de digitação no nosso código?
numbers = [10, 20, 30]
try:
index = 2
value = numbrs[index] # Erro de digitação: 'numbrs' em vez de 'numbers'
print(f"Value at index {index}: {value}")
except:
print("Could not access the list element.")Output:
Could not access the list element.O except nu captura o NameError do erro de digitação e imprime "Could not access the list element" — nos dando a informação errada sobre o que deu errado! A gente acha que o índice está fora do intervalo, mas na verdade temos um erro de digitação no nome da variável.
Um except nu também captura KeyboardInterrupt (quando o usuário aperta Ctrl+C) e SystemExit (quando o programa tenta sair), que normalmente não deveriam ser capturados. Por esses motivos, é melhor capturar exceções específicas, o que vamos aprender a seguir.
25.2) Capturando Exceções Específicas
25.2.1) Especificando Tipos de Exceção
Em vez de capturar todas as exceções com um except nu, podemos especificar quais tipos de exceção queremos tratar. Isso deixa nosso código mais preciso e ajuda a responder adequadamente a erros diferentes:
# Capturando um tipo de exceção específico
user_input = "hello"
try:
number = int(user_input)
print(f"You entered: {number}")
except ValueError:
print("That's not a valid number!")
number = 0
print(f"Using number: {number}")Output:
That's not a valid number!
Using number: 0Agora nossa cláusula except só captura exceções ValueError. Se um tipo diferente de exceção ocorrer (como um NameError por causa de um erro de digitação), ela não vai ser capturada, e a gente vai ver o traceback completo — o que na verdade é útil para debugging!
A sintaxe é: except ExceptionType: onde ExceptionType é o nome da classe de exceção que você quer capturar (como ValueError, TypeError, ZeroDivisionError, etc.).
Erro Comum: Capturar o Tipo de Exceção Errado
O que acontece se você especificar um tipo de exceção que não corresponde ao que realmente ocorre? Vamos ver:
# Capturando o tipo de exceção errado
user_input = "hello"
try:
number = int(user_input) # Isso levanta ValueError
print(f"You entered: {number}")
except TypeError: # Mas estamos capturando TypeError!
print("That's not a valid number!")
number = 0
print(f"Using number: {number}")Output:
Traceback (most recent call last):
File "example.py", line 4, in <module>
number = int(user_input)
ValueError: invalid literal for int() with base 10: 'hello'O programa travou! Por quê? Porque int("hello") levanta um ValueError, mas nossa cláusula except só captura TypeError. Como não existe uma cláusula except correspondente, a exceção não é capturada, e o programa termina.
Isso na verdade ajuda durante o desenvolvimento — se você capturar o tipo de exceção errado, você vai ver o traceback completo e perceber o erro. Esse é um dos motivos pelos quais capturar exceções específicas é melhor do que usar except nu.
Como evitar esse erro:
- Leia o traceback para ver que tipo de exceção realmente ocorreu
- Use esse tipo de exceção específico na sua cláusula except
- Se você não tiver certeza, rode o código e deixe ele travar — o traceback vai te dizer!
25.2.2) Tratando Exceções Diferentes de Formas Diferentes
Você pode ter várias cláusulas except para tratar tipos de exceção diferentes de formas diferentes. Isso é extremamente útil quando erros diferentes exigem respostas diferentes:
# Tratamento diferente para exceções diferentes
print("Enter two numbers to divide:")
numerator_input = input("Numerator: ")
denominator_input = input("Denominator: ")
try:
numerator = int(numerator_input)
denominator = int(denominator_input)
result = numerator / denominator
print(f"{numerator} / {denominator} = {result}")
except ValueError:
print("Error: Both inputs must be valid integers.")
print("You entered something that isn't a number.")
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
print("The denominator must be a non-zero number.")Se o usuário digitar "10" e "abc":
Enter two numbers to divide:
Numerator: 10
Denominator: abc
Error: Both inputs must be valid integers.
You entered something that isn't a number.Se o usuário digitar "10" e "0":
Enter two numbers to divide:
Numerator: 10
Denominator: 0
Error: Cannot divide by zero.
The denominator must be a non-zero number.O Python verifica cada cláusula except em ordem. Quando uma exceção ocorre, o Python encontra a primeira cláusula except que corresponde ao tipo da exceção e executa aquele bloco. As outras cláusulas except são ignoradas.
25.2.3) Capturando Vários Tipos de Exceção em uma Única Cláusula
Às vezes você quer tratar vários tipos diferentes de exceção da mesma forma. Em vez de escrever vários blocos except idênticos, você pode capturar vários tipos de exceção em uma única cláusula colocando-os entre parênteses como uma tupla:
# Capturando vários tipos de exceção juntos
print("Enter a number:")
user_input = input()
try:
number = int(user_input)
result = 100 / number
print(f"100 divided by {number} is {result}")
except (ValueError, ZeroDivisionError):
print("Invalid input or division by zero.")
print("Please enter a non-zero number.")Se o usuário digitar "hello":
Enter a number:
hello
Invalid input or division by zero.
Please enter a non-zero number.Se o usuário digitar "0":
Enter a number:
0
Invalid input or division by zero.
Please enter a non-zero number.Tanto ValueError (por conversão inválida) quanto ZeroDivisionError (por dividir por zero) são tratados pela mesma cláusula except. Isso é útil quando erros diferentes devem disparar a mesma resposta.
25.2.4) Acessando Informações da Exceção
Às vezes você precisa saber mais detalhes sobre a exceção que ocorreu. Você pode capturar o objeto da exceção usando a palavra-chave as. Mas primeiro, vamos entender o que um objeto de exceção realmente é.
O Que É um Objeto de Exceção?
Quando o Python levanta uma exceção, ele não só sinaliza que algo deu errado — ele cria um objeto que contém informações sobre o erro. Esse objeto de exceção é como um relatório detalhado de erro que inclui:
- A mensagem de erro: Uma descrição do que deu errado
- O tipo de exceção: Que tipo de erro ocorreu (ValueError, TypeError, etc.)
- Atributos adicionais: Informações específicas dependendo do tipo de exceção
Pense em um objeto de exceção como um contêiner que guarda todas as informações sobre um erro. Assim como um objeto de lista contém itens e tem métodos como append(), um objeto de exceção contém informações de erro e tem atributos que você pode acessar.
Quando você escreve except ValueError as error:, você está dizendo ao Python: "Se um ValueError acontecer, crie uma variável chamada error e coloque o objeto da exceção nela para que eu possa examiná-lo."
Vamos explorar o que existe dentro de um objeto de exceção:
# Examinando o conteúdo do objeto de exceção
try:
number = int("hello")
except ValueError as error:
print("Exception caught! Let's examine it:")
print(f"Type: {type(error)}")
print(f"String representation: {error}")
print(f"Args tuple: {error.args}")Output:
Exception caught! Let's examine it:
Type: <class 'ValueError'>
String representation: invalid literal for int() with base 10: 'hello'
Args tuple: ("invalid literal for int() with base 10: 'hello'",)O objeto de exceção tem:
- Um tipo (classe ValueError) — isso diz que tipo de erro ocorreu
- Uma representação em string (a mensagem de erro) — isso é o que você vê nos tracebacks
- Um atributo args (tupla contendo a mensagem e quaisquer outros argumentos) — isso fornece acesso estruturado aos detalhes do erro
Por que isso importa:
Tipos diferentes de exceção têm atributos diferentes que fornecem informações específicas. Entender a estrutura de objetos de exceção te ajuda a extrair informações úteis para debugging ou feedback ao usuário:
# Exceções diferentes têm atributos diferentes
numbers = [10, 20, 30]
try:
value = numbers[10]
except IndexError as error:
print(f"IndexError message: {error}")
print(f"Exception args: {error.args}")
# Agora tente com um dicionário
grades = {"Alice": 95}
try:
grade = grades["Bob"]
except KeyError as error:
print(f"KeyError message: {error}")
print(f"Missing key: {error.args[0]}")Output:
IndexError message: list index out of range
Exception args: ('list index out of range',)
KeyError message: 'Bob'
Missing key: BobRepare como KeyError inclui a chave real que estava faltando na mensagem. Tipos diferentes de exceção fornecem informações úteis diferentes que você pode acessar por meio do objeto de exceção.
25.3) Usando else e finally com Blocos try
25.3.1) A Cláusula else: Código Que Roda Somente no Sucesso
A cláusula else em um bloco try-except roda apenas se nenhuma exceção ocorreu no bloco try. Isso é útil para código que só deve executar quando a operação arriscada dá certo:
# Usando else para código que só roda no sucesso
print("Enter a number:")
user_input = input()
try:
number = int(user_input)
except ValueError:
print("That's not a valid number!")
else:
# Isso só roda se int(user_input) deu certo
print(f"Successfully converted: {number}")
squared = number ** 2
print(f"The square of {number} is {squared}")Se o usuário digitar "5":
Enter a number:
5
Successfully converted: 5
The square of 5 is 25Se o usuário digitar "hello":
Enter a number:
hello
That's not a valid number!Por que usar else em vez de simplesmente colocar o código no final do bloco try? Existem dois motivos importantes:
- Clareza: A cláusula
elsedeixa explícito que esse código só roda no sucesso - Escopo de exceções: Exceções levantadas na cláusula
elsenão são capturadas pelas cláusulasexceptanteriores
Aqui vai um exemplo mostrando por que o segundo ponto importa:
# Demonstrando por que else é útil para o escopo de exceção
try:
number_1 = int(input("Enter a number_1: "))
except ValueError:
print("Invalid input!")
else:
# Se um erro ocorrer aqui, ele não será capturado pelo except acima
# Isso ajuda a distinguir entre erros de entrada e erros de processamento
number_2 = int(input("Enter a number_2: ")) # Pode levantar ValueErrorSe colocarmos number_2 = int(input(...)) no bloco try junto com number_1, qualquer ValueError de qualquer uma das entradas seria capturado pela mesma cláusula except ValueError. Isso torna impossível saber qual entrada causou o problema.
Ao colocar number_2 = int(input(...)) no bloco else, nós separamos o tratamento de erros. A cláusula except só captura erros de number_1, enquanto erros de number_2 vão levantar uma exceção não capturada com um traceback completo — deixando claro que a segunda entrada falhou, e não a primeira.
25.3.2) A Cláusula finally: Código Que Sempre Roda
A cláusula finally contém código que roda não importa o quê — se uma exceção ocorreu ou não, se ela foi capturada ou não. Isso é essencial para operações de limpeza (cleanup) que sempre precisam acontecer:
# Usando finally para limpeza
print("Enter a number:")
user_input = input()
try:
number = int(user_input)
result = 100 / number
print(f"Result: {result}")
except ValueError:
print("Invalid number!")
except ZeroDivisionError:
print("Cannot divide by zero!")
finally:
print("Calculation attempt completed.")Se o usuário digitar "5":
Enter a number:
5
Result: 20.0
Calculation attempt completed.Se o usuário digitar "hello":
Enter a number:
hello
Invalid number!
Calculation attempt completed.Se o usuário digitar "0":
Enter a number:
0
Cannot divide by zero!
Calculation attempt completed.O bloco finally roda nos três casos! Esse é o comportamento-chave do finally: ele sempre executa, independentemente do que aconteceu no bloco try.
25.3.3) Combinando try, except, else e finally
Você pode usar as quatro cláusulas juntas para criar um tratamento de exceções completo:
# Estrutura completa de tratamento de exceções
print("Enter a number to calculate its reciprocal:")
user_input = input()
try:
# Operações arriscadas
number = int(user_input)
reciprocal = 1 / number
except ValueError:
# Tratar erros de conversão
print("Error: Input must be a valid integer.")
except ZeroDivisionError:
# Tratar divisão por zero
print("Error: Cannot calculate reciprocal of zero.")
else:
# Código que só roda no sucesso
print(f"The reciprocal of {number} is {reciprocal}")
print(f"Verification: {number} × {reciprocal} = {number * reciprocal}")
finally:
# Código de limpeza que sempre roda
print("Reciprocal calculation completed.")Se o usuário digitar "4":
Enter a number to calculate its reciprocal:
4
The reciprocal of 4 is 0.25
Verification: 4 × 0.25 = 1.0
Reciprocal calculation completed.Se o usuário digitar "hello":
Enter a number to calculate its reciprocal:
hello
Error: Input must be a valid integer.
Reciprocal calculation completed.Se o usuário digitar "0":
Enter a number to calculate its reciprocal:
0
Error: Cannot calculate reciprocal of zero.
Reciprocal calculation completed.O fluxo de execução é:
- O bloco
trysempre executa primeiro - Se uma exceção ocorrer, o bloco
exceptcorrespondente executa - Se nenhuma exceção ocorrer, o bloco
elseexecuta (se existir) - O bloco
finallysempre executa por último, independentemente do que aconteceu
25.4) Levantando Exceções Deliberadamente com raise
25.4.1) Por Que Levantar Exceções?
Até aqui, a gente vem capturando exceções que o Python levanta automaticamente. Mas às vezes você precisa levantar uma exceção deliberadamente no seu próprio código. Isso é útil quando:
- Você detecta uma situação inválida que seu código não consegue tratar
- Você quer impor regras ou restrições
- Você quer sinalizar um erro para o código que chamou sua função(function)
Levantar uma exceção é a forma do Python dizer: "não consigo continuar — algo está errado, e quem me chamou precisa lidar com isso."
A sintaxe é simples: raise ExceptionType("error message")
Aqui vai um exemplo básico:
# Levantando uma exceção deliberadamente
age = -5
if age < 0:
raise ValueError("Age cannot be negative!")
print(f"Age: {age}") # Esta linha nunca executaOutput:
Traceback (most recent call last):
File "example.py", line 5, in <module>
raise ValueError("Age cannot be negative!")
ValueError: Age cannot be negative!Quando o Python encontra raise, ele imediatamente cria uma exceção e começa a procurar um bloco except para tratá-la. Se não houver, o programa termina com um traceback.
25.4.2) Levantando Exceções em Funções
Levantar exceções é particularmente útil em funções(functions) para validar entradas e impor restrições:
# Função que valida entrada levantando exceções
def calculate_discount(price, discount_percent):
"""Calculate discounted price.
Args:
price: Original price (must be positive)
discount_percent: Discount percentage (must be 0-100)
Returns:
Discounted price
Raises:
ValueError: If inputs are invalid
"""
if price < 0:
raise ValueError("Price cannot be negative!")
if discount_percent < 0 or discount_percent > 100:
raise ValueError("Discount must be between 0 and 100!")
discount_amount = price * (discount_percent / 100)
return price - discount_amount
# Usando a função
try:
final_price = calculate_discount(100, 20)
print(f"Final price: ${final_price}")
except ValueError as error:
print(f"Error: {error}")Output:
Final price: $80.0Agora vamos tentar com entradas inválidas:
# Preço inválido
try:
final_price = calculate_discount(-50, 20)
print(f"Final price: ${final_price}")
except ValueError as error:
print(f"Error: {error}")Output:
Error: Price cannot be negative!# Desconto inválido
try:
final_price = calculate_discount(100, 150)
print(f"Final price: ${final_price}")
except ValueError as error:
print(f"Error: {error}")Output:
Error: Discount must be between 0 and 100!Ao levantar exceções, a função comunica claramente o que deu errado. O código que chama pode então decidir como tratar o erro — talvez pedindo ao usuário uma nova entrada, usando valores padrão ou registrando o erro.
25.4.3) Escolhendo o Tipo de Exceção Certo
O Python tem muitos tipos de exceção embutidos, e escolher o certo deixa seu código mais claro. Aqui estão as exceções mais usadas para validação:
- ValueError: Use quando um valor tem o tipo certo, mas um valor inadequado (por exemplo, idade negativa, porcentagem inválida)
- TypeError: Use quando um valor é de um tipo completamente errado (por exemplo, string em vez de número)
- KeyError: Use quando uma chave do dicionário não existe
- IndexError: Use quando um índice de sequência está fora do intervalo
Aqui vai um exemplo mostrando diferentes tipos de exceção:
# Usando tipos de exceção apropriados
def get_student_grade(grades, student_name):
"""Get a student's grade from the grades dictionary.
Args:
grades: Dictionary mapping student names to grades
student_name: Name of the student
Returns:
The student's grade
Raises:
TypeError: If grades is not a dictionary
KeyError: If student_name is not in grades
ValueError: If the grade is invalid
"""
if not isinstance(grades, dict):
raise TypeError("Grades must be a dictionary!")
if student_name not in grades:
raise KeyError(f"Student '{student_name}' not found!")
grade = grades[student_name]
if not (0 <= grade <= 100):
raise ValueError(f"Invalid grade: {grade} (must be 0-100)")
return grade
# Testar com dados válidos
grades = {"Alice": 95, "Bob": 87, "Carol": 92}
try:
grade = get_student_grade(grades, "Alice")
print(f"Alice's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
print(f"Error: {error}")Output:
Alice's grade: 95# Testar com aluno faltando
try:
grade = get_student_grade(grades, "David")
print(f"David's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
print(f"Error: {error}")Output:
Error: Student 'David' not found!# Testar com tipo errado
try:
grade = get_student_grade("not a dict", "Alice")
print(f"Alice's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
print(f"Error: {error}")Output:
Error: Grades must be a dictionary!Usar o tipo de exceção apropriado ajuda outros programadores (e você no futuro) a entender que tipo de erro ocorreu.
25.4.4) Relançando Exceções
Às vezes você quer capturar uma exceção, fazer algo (como registrar log) e então deixar a exceção continuar propagando. Você pode fazer isso usando raise sem argumentos dentro de um bloco except:
# Relançando uma exceção após registrar logs
def divide_numbers(a, b):
"""Divide two numbers with error logging."""
try:
result = a / b
return result
except ZeroDivisionError:
print("ERROR LOG: Division by zero attempted")
print(f" Numerator: {a}, Denominator: {b}")
raise # Relançar a mesma exceção
# Usando a função
try:
result = divide_numbers(10, 0)
print(f"Result: {result}")
except ZeroDivisionError:
print("Cannot divide by zero!")Output:
ERROR LOG: Division by zero attempted
Numerator: 10, Denominator: 0
Cannot divide by zero!A instrução raise sem argumentos relança a exceção que acabou de ser capturada. Isso é útil quando você quer:
- Registrar (logar) ou gravar o erro
- Fazer alguma limpeza
- Deixar o erro propagar para quem chamou
25.4.5) Levantando Exceções a Partir de Exceções
Às vezes você quer levantar uma nova exceção enquanto trata outra, preservando o contexto do erro original. O Python 3 fornece a sintaxe raise ... from ... para isso:
# Levantando uma nova exceção a partir de uma existente
def load_config(config_dict, key):
"""Load configuration value from dictionary."""
try:
config_value = config_dict[key]
# Tentar fazer o parse como inteiro
parsed_value = int(config_value)
return parsed_value
except KeyError as error:
raise RuntimeError(f"Configuration key missing: {key}") from error
except ValueError as error:
raise RuntimeError(f"Invalid configuration format for {key}") from error
# Usando a função
config = {"timeout": "30", "retries": "5"}
try:
value = load_config(config, "timeout")
print(f"Config value: {value}")
except RuntimeError as error:
print(f"Configuration error: {error}")
print(f"Original cause: {error.__cause__}")Output:
Config value: 30Se a chave não existir:
try:
value = load_config(config, "missing_key")
print(f"Config value: {value}")
except RuntimeError as error:
print(f"Configuration error: {error}")
print(f"Original cause: {error.__cause__}")Output:
Configuration error: Configuration key missing: missing_key
Original cause: 'missing_key'A palavra-chave from vincula a nova exceção à original. Isso cria uma cadeia de exceções que ajuda no debugging — você consegue ver tanto o que deu errado em alto nível (erro de configuração) quanto qual foi a causa subjacente (chave não encontrada).
O tratamento de exceções é uma das ferramentas mais importantes para escrever programas confiáveis. Ao usar blocos try-except, você consegue antecipar problemas, tratá-los com elegância e oferecer uma experiência melhor para seus usuários. Lembre-se:
- Use
try-exceptpara tratar erros esperados com elegância - Capture tipos específicos de exceção em vez de usar
exceptnu - Use
elsepara código que só deve rodar no sucesso - Use
finallypara código de limpeza que sempre precisa rodar - Levante exceções no seu próprio código para sinalizar problemas
- Escolha tipos de exceção apropriados para deixar os erros claros
- Forneça mensagens de erro úteis que expliquem o que deu errado
No próximo capítulo, vamos aprender técnicas de programação defensiva que combinam tratamento de exceções com validação de entrada e outras estratégias para deixar nossos programas ainda mais robustos.