Todo programa que interage com o mundo real encontra situações inesperadas — arquivos que não existem, conexões que falham, dados no formato errado, divisões por zero. Um programa robusto não apenas funciona no caminho feliz — ele lida graciosamente com falhas. Python oferece um sistema de exceções expressivo e flexível para isso.
O que é uma Exceção?
Uma exceção é um objeto que representa um erro ocorrido durante a execução. Quando Python encontra um problema, ele lança (raises) uma exceção. Se não houver código para tratá-la, o programa encerra com uma mensagem de erro chamada traceback:
numeros = [1, 2, 3]
print(numeros[10])
IndexError: list index out of range
Traceback (most recent call last):
File "exemplo.py", line 2, in <module>
print(numeros[10])
O traceback deve ser lido de baixo para cima — a última linha indica o erro; as linhas acima mostram o caminho de chamadas que levou até ele.
Exceções Comuns
# TypeError — operação em tipo errado
"texto" + 42
# ValueError — tipo certo, valor errado
int("abc")
# KeyError — chave inexistente em dicionário
{"a": 1}["b"]
# IndexError — índice fora do intervalo
[][0]
# AttributeError — atributo inexistente
"texto".voar()
# ZeroDivisionError — divisão por zero
10 / 0
# FileNotFoundError — arquivo inexistente
open("nao_existe.txt")
# ImportError — módulo não encontrado
import modulo_inexistente
# NameError — variável não definida
print(variavel_nao_definida)
try / except
A estrutura básica de tratamento:
def dividir(a, b):
try:
resultado = a / b
return resultado
except ZeroDivisionError:
print("Erro: divisão por zero.")
return None
print(dividir(10, 2)) # 5.0
print(dividir(10, 0)) # Erro: divisão por zero. → None
Capturando Múltiplas Exceções
def converter_e_processar(texto, indice):
try:
numero = int(texto)
lista = [1, 2, 3, 4, 5]
elemento = lista[indice]
return numero + elemento
except ValueError:
print(f"'{texto}' não é um número válido.")
except IndexError:
print(f"Índice {indice} fora do intervalo.")
except (TypeError, AttributeError) as e:
print(f"Erro de tipo ou atributo: {e}")
return None
print(converter_e_processar("10", 2)) # 13
print(converter_e_processar("abc", 2)) # 'abc' não é um número válido.
print(converter_e_processar("10", 99)) # Índice 99 fora do intervalo.
Evite capturar Exception ou BaseException de forma genérica sem necessidade — isso esconde erros reais e dificulta o diagnóstico.
else e finally
A estrutura completa do tratamento de exceções:
def ler_arquivo(caminho):
arquivo = None
try:
arquivo = open(caminho, "r", encoding="utf-8")
conteudo = arquivo.read()
except FileNotFoundError:
print(f"Arquivo '{caminho}' não encontrado.")
return None
except PermissionError:
print(f"Sem permissão para ler '{caminho}'.")
return None
else:
# Executado apenas se nenhuma exceção ocorreu
print(f"Arquivo lido com sucesso: {len(conteudo)} caracteres.")
return conteudo
finally:
# Executado SEMPRE — com ou sem exceção
if arquivo:
arquivo.close()
print("Arquivo fechado.")
conteudo = ler_arquivo("dados.txt")
O bloco finally é garantido mesmo se houver return dentro do try — essencial para liberar recursos.
O Gerenciador de Contexto: with
A forma mais Pythônica de gerenciar recursos é com with — que chama finally automaticamente:
# Sem with — verboso e frágil
arquivo = open("dados.txt", "r")
try:
conteudo = arquivo.read()
finally:
arquivo.close()
# Com with — limpo e seguro
with open("dados.txt", "r", encoding="utf-8") as arquivo:
conteudo = arquivo.read()
# arquivo.close() é chamado automaticamente aqui
Lançando Exceções: raise
def calcular_raiz(numero):
if numero < 0:
raise ValueError(f"Não é possível calcular raiz de número negativo: {numero}")
return numero ** 0.5
def sacar(saldo, valor):
if valor <= 0:
raise ValueError("O valor do saque deve ser positivo.")
if valor > saldo:
raise ValueError(f"Saldo insuficiente. Saldo: R${saldo:.2f}, Solicitado: R${valor:.2f}")
return saldo - valor
try:
print(calcular_raiz(-4))
except ValueError as e:
print(f"Erro: {e}")
try:
novo_saldo = sacar(100, 150)
except ValueError as e:
print(f"Erro: {e}")
Re-lançando Exceções
import logging
def processar_pagamento(valor):
try:
if valor <= 0:
raise ValueError("Valor inválido.")
print(f"Pagamento de R${valor:.2f} processado.")
except ValueError as e:
logging.error(f"Falha no pagamento: {e}")
raise # re-lança a mesma exceção para o chamador tratar
def finalizar_compra(valor):
try:
processar_pagamento(valor)
except ValueError:
print("Compra cancelada por valor inválido.")
finalizar_compra(-50)
Exceções Personalizadas
Para sistemas maiores, crie sua própria hierarquia de exceções:
class ErroAplicacao(Exception):
"""Exceção base da aplicação."""
pass
class ErroValidacao(ErroAplicacao):
"""Erro de validação de dados."""
def __init__(self, campo, mensagem):
self.campo = campo
self.mensagem = mensagem
super().__init__(f"Validação falhou no campo '{campo}': {mensagem}")
class ErroAutenticacao(ErroAplicacao):
"""Erro de autenticação."""
def __init__(self, usuario):
self.usuario = usuario
super().__init__(f"Autenticação falhou para o usuário '{usuario}'.")
class ErroRecursoNaoEncontrado(ErroAplicacao):
"""Recurso não encontrado."""
def __init__(self, recurso, identificador):
self.recurso = recurso
self.identificador = identificador
super().__init__(f"{recurso} com id={identificador} não encontrado.")
def buscar_usuario(usuario_id, banco):
usuario = banco.get(usuario_id)
if not usuario:
raise ErroRecursoNaoEncontrado("Usuário", usuario_id)
return usuario
def autenticar(usuario, senha):
if usuario.get("senha") != senha:
raise ErroAutenticacao(usuario.get("nome"))
return True
def atualizar_email(usuario, novo_email):
if "@" not in novo_email:
raise ErroValidacao("email", "formato inválido")
usuario["email"] = novo_email
banco = {1: {"nome": "Ana", "senha": "1234", "email": "ana@email.com"}}
try:
u = buscar_usuario(1, banco)
autenticar(u, "1234")
atualizar_email(u, "novo-email-sem-arroba")
except ErroRecursoNaoEncontrado as e:
print(f"[404] {e}")
except ErroAutenticacao as e:
print(f"[401] {e}")
except ErroValidacao as e:
print(f"[422] Campo '{e.campo}': {e.mensagem}")
except ErroAplicacao as e:
print(f"[500] Erro interno: {e}")
Encadeamento de Exceções
def carregar_config(caminho):
try:
with open(caminho) as f:
import json
return json.load(f)
except FileNotFoundError as e:
raise ErroAplicacao(f"Configuração não encontrada: {caminho}") from e
except json.JSONDecodeError as e:
raise ErroAplicacao(f"Configuração inválida em: {caminho}") from e
try:
config = carregar_config("config.json")
except ErroAplicacao as e:
print(f"Erro: {e}")
print(f"Causa original: {e.__cause__}")
O from e preserva a exceção original como causa — visível no traceback e em __cause__.
Exemplo Completo: API de Cadastro
class ErroAplicacao(Exception):
pass
class ErroValidacao(ErroAplicacao):
def __init__(self, erros: dict):
self.erros = erros
super().__init__(f"Erros de validação: {erros}")
class ErroConflito(ErroAplicacao):
pass
class CadastroUsuario:
def __init__(self):
self._usuarios = {}
def _validar(self, dados: dict) -> dict:
erros = {}
if not dados.get("nome") or len(dados["nome"]) < 2:
erros["nome"] = "Nome deve ter ao menos 2 caracteres."
if not dados.get("email") or "@" not in dados["email"]:
erros["email"] = "E-mail inválido."
if not dados.get("senha") or len(dados["senha"]) < 6:
erros["senha"] = "Senha deve ter ao menos 6 caracteres."
return erros
def cadastrar(self, dados: dict) -> dict:
erros = self._validar(dados)
if erros:
raise ErroValidacao(erros)
email = dados["email"]
if email in self._usuarios:
raise ErroConflito(f"E-mail '{email}' já cadastrado.")
usuario = {
"id": len(self._usuarios) + 1,
"nome": dados["nome"],
"email": email,
}
self._usuarios[email] = usuario
return usuario
cadastro = CadastroUsuario()
casos = [
{"nome": "Ana Silva", "email": "ana@email.com", "senha": "segura123"},
{"nome": "A", "email": "invalido", "senha": "123"},
{"nome": "Ana Silva", "email": "ana@email.com", "senha": "outrasenha"},
]
for dados in casos:
try:
usuario = cadastro.cadastrar(dados)
print(f"[OK] Usuário criado: {usuario}")
except ErroValidacao as e:
print(f"[VALIDAÇÃO] {e.erros}")
except ErroConflito as e:
print(f"[CONFLITO] {e}")
Resumo
- Exceções são objetos que representam erros em tempo de execução
try/exceptcaptura exceções;elseexecuta se não houver erro;finallyexecuta sempre- Use
withpara gerenciar recursos — substituitry/finallypara abertura de arquivos e conexões raiselança exceções explicitamente;raise ... from epreserva a causa original- Crie hierarquias de exceções personalizadas para sistemas com domínio bem definido
- Capture exceções específicas — evite capturar
Exceptiongenericamente - Leia tracebacks de baixo para cima — a última linha indica o ponto exato do erro
Referências e Leituras Complementares
- Exceções embutidas — https://docs.python.org/3/library/exceptions.html
- Tratamento de erros — tutorial oficial — https://docs.python.org/3/tutorial/errors.html
- PEP 3134 — encadeamento de exceções — https://peps.python.org/pep-3134/
- contextlib — utilitários para gerenciadores de contexto — https://docs.python.org/3/library/contextlib.html
- BEAZLEY, David; JONES, Brian K. Python Cookbook. 3. ed. O'Reilly Media, 2013. Cap. 14 — tratamento avançado de exceções e logging.
- RAMALHO, Luciano. Fluent Python. 2. ed. O'Reilly Media, 2022. Cap. 18 — gerenciadores de contexto e blocos with.
- HUNT, John. Advanced Guide to Python 3 Programming. Springer, 2019. Cap. 6 — exceções e debugging em aplicações reais.