Exceções: try, except, finally e raise
1. Introdução ao Tratamento de Exceções em Python
Exceções são eventos que ocorrem durante a execução de um programa e interrompem o fluxo normal de instruções. Diferentemente de erros de sintaxe, que impedem o programa de sequer ser executado, as exceções acontecem em tempo de execução e podem ser tratadas para evitar que o programa seja abruptamente encerrado.
Em Python, toda exceção é uma classe que herda de BaseException. A hierarquia principal inclui:
BaseException— classe base para todas as exceçõesException— classe base para exceções que não devem ser ignoradas (herda deBaseException)ArithmeticError,LookupError,ValueError,TypeError, etc. — subclasses deException
Tratar exceções permite que você controle como seu programa reage a situações inesperadas, como arquivos inexistentes, divisões por zero ou entradas inválidas do usuário.
2. O Bloco try e except: Capturando Exceções
A estrutura mais básica para tratamento de exceções utiliza try e except:
try:
numero = int(input("Digite um número: "))
resultado = 10 / numero
print(f"Resultado: {resultado}")
except ValueError:
print("Erro: você não digitou um número válido!")
except ZeroDivisionError:
print("Erro: não é possível dividir por zero!")
Você pode capturar múltiplas exceções em um único bloco except usando uma tupla:
try:
arquivo = open("dados.txt", "r")
conteudo = arquivo.read()
numero = int(conteudo)
except (FileNotFoundError, ValueError) as erro:
print(f"Erro ao processar arquivo: {erro}")
3. Especificando o Tipo de Exceção e Acessando Detalhes
Para acessar informações detalhadas sobre a exceção, utilize a sintaxe as:
try:
lista = [1, 2, 3]
print(lista[5])
except IndexError as e:
print(f"Tipo do erro: {type(e).__name__}")
print(f"Mensagem: {e}")
print(f"Argumentos: {e.args}")
Evite usar except: sem especificar o tipo de exceção, pois isso captura até mesmo KeyboardInterrupt e SystemExit, o que pode mascarar problemas graves e dificultar a depuração.
# Ruim: captura tudo, inclusive erros que deveriam interromper o programa
try:
operacao_arriscada()
except:
print("Algo deu errado")
# Bom: captura apenas exceções específicas
try:
operacao_arriscada()
except (ValueError, TypeError) as e:
print(f"Erro esperado: {e}")
4. O Bloco else: Executando Código Quando Não Há Exceção
O bloco else é executado apenas quando nenhuma exceção ocorre no bloco try. Isso ajuda a separar o código que pode gerar exceções do código que deve rodar apenas em caso de sucesso:
try:
idade = int(input("Digite sua idade: "))
except ValueError:
print("Idade inválida!")
else:
print(f"Você tem {idade} anos.")
if idade >= 18:
print("Você é maior de idade.")
Colocar código no else em vez de no try evita capturar exceções inesperadas que poderiam ocorrer em operações que você não pretendia proteger.
5. O Bloco finally: Garantindo Limpeza e Finalização
O bloco finally é executado sempre, independentemente de ocorrer ou não uma exceção. É o local ideal para liberar recursos:
arquivo = None
try:
arquivo = open("relatorio.txt", "w")
arquivo.write("Dados importantes")
# Simulando um erro
resultado = 1 / 0
except ZeroDivisionError:
print("Erro matemático durante a escrita!")
finally:
if arquivo:
arquivo.close()
print("Arquivo fechado com sucesso.")
A ordem de execução é: try → except (se houver exceção) ou else (se não houver) → finally.
6. Levantando Exceções com raise
Você pode levantar exceções intencionalmente com raise:
def dividir(a, b):
if b == 0:
raise ValueError("O divisor não pode ser zero!")
return a / b
try:
resultado = dividir(10, 0)
except ValueError as erro:
print(f"Erro: {erro}")
Para relançar uma exceção capturada, use raise sem argumentos:
try:
numero = int(input("Digite um número positivo: "))
if numero < 0:
raise ValueError("Número negativo não permitido!")
except ValueError as erro:
print(f"Erro capturado: {erro}")
raise # Relança a exceção para ser tratada em nível superior
Para criar exceções personalizadas, herde de Exception:
class SaldoInsuficienteError(Exception):
def __init__(self, saldo, valor_saque):
self.saldo = saldo
self.valor_saque = valor_saque
super().__init__(f"Saldo insuficiente: R${saldo:.2f} para saque de R${valor_saque:.2f}")
def sacar(conta, valor):
if valor > conta["saldo"]:
raise SaldoInsuficienteError(conta["saldo"], valor)
conta["saldo"] -= valor
return conta["saldo"]
7. Combinando try, except, else, finally e raise
Um exemplo completo que demonstra a interação entre todos os blocos:
def processar_dados(arquivo_nome):
dados = None
try:
with open(arquivo_nome, "r") as arquivo:
dados = arquivo.read()
if not dados.strip():
raise ValueError("Arquivo vazio!")
numeros = [int(x) for x in dados.split(",")]
except FileNotFoundError:
print(f"Arquivo '{arquivo_nome}' não encontrado.")
return
except ValueError as e:
print(f"Erro nos dados: {e}")
return
else:
media = sum(numeros) / len(numeros)
print(f"Média dos números: {media:.2f}")
return numeros
finally:
print("Processamento finalizado.")
# Testando diferentes cenários
print("=== Cenário 1: Arquivo inexistente ===")
processar_dados("inexistente.txt")
print("\n=== Cenário 2: Dados inválidos ===")
# Supondo que 'dados_invalidos.txt' contenha "1,2,abc,4"
processar_dados("dados_invalidos.txt")
print("\n=== Cenário 3: Sucesso ===")
# Supondo que 'dados_validos.txt' contenha "10,20,30,40"
processar_dados("dados_validos.txt")
Neste exemplo:
- Se o arquivo não existe, FileNotFoundError é capturado e a função retorna
- Se os dados são inválidos, ValueError é capturado
- Se tudo ocorre bem, o bloco else calcula a média
- O bloco finally sempre executa, indicando o fim do processamento
8. Erros Comuns e Dicas Finais
Erro 1: Capturar exceções genéricas
# Ruim
try:
resultado = operacao_complexa()
except:
pass # Erros importantes são ignorados
# Bom
try:
resultado = operacao_complexa()
except (ValueError, TypeError) as e:
logger.error(f"Erro esperado: {e}")
Erro 2: Não liberar recursos adequadamente
# Ruim: arquivo pode não ser fechado se ocorrer exceção
arquivo = open("dados.txt", "r")
dados = arquivo.read()
if not dados:
raise ValueError("Arquivo vazio")
arquivo.close()
# Bom: usando finally ou gerenciador de contexto
with open("dados.txt", "r") as arquivo:
dados = arquivo.read()
if not dados:
raise ValueError("Arquivo vazio")
# Arquivo é fechado automaticamente
Erro 3: Aninhamento excessivo de blocos try/except
# Ruim: difícil de ler e manter
try:
try:
arquivo = open("dados.txt")
try:
numero = int(arquivo.read())
except ValueError:
print("Erro de conversão")
except FileNotFoundError:
print("Arquivo não encontrado")
finally:
arquivo.close()
# Bom: blocos enxutos e separados
def ler_numero_do_arquivo(nome_arquivo):
with open(nome_arquivo, "r") as arquivo:
return int(arquivo.read())
try:
numero = ler_numero_do_arquivo("dados.txt")
except FileNotFoundError:
print("Arquivo não encontrado")
except ValueError:
print("Erro de conversão")
Use raise quando a função não puder tratar adequadamente o erro e precisar delegar a responsabilidade para quem a chamou. Trate localmente apenas quando você puder oferecer uma alternativa viável ou uma mensagem de erro significativa para o usuário.
Referências
- Documentação oficial: Erros e Exceções (Python 3) — Tutorial completo sobre tratamento de exceções na documentação oficial do Python.
- PEP 8 – Guia de Estilo para Código Python — Recomendações de estilo para tratamento de exceções, incluindo boas práticas para uso de
except. - Python Exceptions: Uma Introdução Prática (Real Python) — Artigo detalhado com exemplos práticos sobre todos os blocos de tratamento de exceções.
- Documentação: Hierarquia de Exceções Internas — Lista completa de todas as exceções nativas do Python e sua hierarquia de classes.
- Gerenciadores de Contexto e a declaração
with(Python.org) — Documentação oficial sobre como gerenciar recursos de forma segura, alternativa ao uso definally. - Python Exception Handling Patterns (GeeksforGeeks) — Padrões comuns de tratamento de exceções com exemplos de código para diferentes cenários.