Python

Tratamento de Exceções e Erros Já leu

8 min de leitura

Tratamento de Exceções e Erros
Todo programa que interage com o mundo real encontra situações inesperadas — arquivos que não existem, conexões que falham, da

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/except captura exceções; else executa se não houver erro; finally executa sempre
  • Use with para gerenciar recursos — substitui try/finally para abertura de arquivos e conexões
  • raise lança exceções explicitamente; raise ... from e preserva a causa original
  • Crie hierarquias de exceções personalizadas para sistemas com domínio bem definido
  • Capture exceções específicas — evite capturar Exception genericamente
  • 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.
Comentários

Mais em Python

Classes e Objetos: os fundamentos da Orientação a Objetos
Classes e Objetos: os fundamentos da Orientação a Objetos

At&eacute; aqui trabalhamos com fun&ccedil;&otilde;es e estruturas de dados s...

Recursão e Estruturas de Dados Avançadas: pilhas, filas e árvores
Recursão e Estruturas de Dados Avançadas: pilhas, filas e árvores

Recurs&atilde;o e estruturas de dados avan&ccedil;adas s&atilde;o o ponto em...

Dominando o Python
Dominando o Python

Se voc&ecirc; j&aacute; pesquisou sobre programa&ccedil;&atilde;o nos &uacute...