Tratamento de Erros em Python — 2025 | Python Brasil

Domine o tratamento de erros em Python com try/except, exceções customizadas e boas práticas. Aprenda agora!

6 min de leitura Equipe Python Brasil

Erros fazem parte do desenvolvimento de software. A diferença entre um programa amador e um profissional está em como esses erros são tratados. Python possui um sistema robusto de exceções que permite capturar, tratar e até criar seus próprios tipos de erro, garantindo que seu programa se comporte de maneira previsível mesmo em situações inesperadas.

Neste artigo, vamos explorar desde o básico do try/except até técnicas avançadas como exceções personalizadas e padrões de tratamento de erros usados em projetos profissionais.

Entendendo exceções em Python

Uma exceção é um evento que interrompe o fluxo normal de execução do programa. Quando algo dá errado, Python levanta (raises) uma exceção. Se ela não for tratada, o programa encerra com uma mensagem de erro.

# Exemplos de exceções comuns
# print(10 / 0)              # ZeroDivisionError
# print(int("abc"))           # ValueError
# lista = [1, 2, 3]
# print(lista[10])            # IndexError
# dicionario = {"a": 1}
# print(dicionario["b"])      # KeyError
# import modulo_inexistente   # ModuleNotFoundError

Cada tipo de exceção indica uma categoria específica de problema. Conhecer os tipos mais comuns ajuda a escrever tratamentos mais precisos.

Try, except, else e finally

A estrutura completa de tratamento de exceções em Python tem quatro blocos:

def dividir(a, b):
    """Demonstra a estrutura completa de tratamento de exceções."""
    try:
        resultado = a / b
    except ZeroDivisionError:
        print("Erro: divisão por zero não é permitida.")
        return None
    except TypeError as e:
        print(f"Erro de tipo: {e}")
        return None
    else:
        # Executado apenas se NÃO houve exceção
        print(f"Divisão realizada com sucesso: {a} / {b}")
        return resultado
    finally:
        # Executado SEMPRE, com ou sem exceção
        print("Operação de divisão finalizada.")

# Testando
print(dividir(10, 3))    # Sucesso
print(dividir(10, 0))    # ZeroDivisionError
print(dividir("10", 3))  # TypeError

O bloco else é frequentemente subutilizado, mas é valioso: ele deixa claro que determinado código só deve executar quando não houve erro. O bloco finally é ideal para liberar recursos como conexões de banco de dados e arquivos abertos.

Capturando múltiplas exceções

Existem diferentes formas de capturar mais de um tipo de exceção:

def processar_dado(valor):
    """Processa um dado com múltiplos tratamentos."""
    try:
        numero = int(valor)
        resultado = 100 / numero
        return resultado
    except (ValueError, TypeError) as e:
        # Captura múltiplas exceções no mesmo bloco
        print(f"Erro na conversão: {e}")
        return None
    except ZeroDivisionError:
        print("O valor não pode ser zero.")
        return None

# Testando
print(processar_dado("5"))     # 20.0
print(processar_dado("abc"))   # Erro na conversão
print(processar_dado("0"))     # Não pode ser zero
print(processar_dado(None))    # Erro na conversão

Evite capturar Exception genericamente sem necessidade. Tratar exceções específicas torna o código mais previsível e facilita a identificação de problemas.

Levantando exceções

Às vezes, é seu código que precisa sinalizar um erro. Use raise para levantar exceções:

def validar_idade(idade):
    """Valida se a idade está dentro dos limites aceitáveis."""
    if not isinstance(idade, int):
        raise TypeError(f"Idade deve ser um inteiro, recebeu {type(idade).__name__}")
    if idade < 0:
        raise ValueError("Idade não pode ser negativa.")
    if idade > 150:
        raise ValueError("Idade não pode ser maior que 150.")
    return True

def cadastrar_usuario(nome, idade):
    """Cadastra um usuário com validação."""
    try:
        validar_idade(idade)
        print(f"Usuário '{nome}' cadastrado com idade {idade}.")
    except (TypeError, ValueError) as e:
        print(f"Erro no cadastro: {e}")

cadastrar_usuario("Ana", 25)        # Sucesso
cadastrar_usuario("Bruno", -5)      # Erro: negativa
cadastrar_usuario("Carla", "vinte") # Erro: tipo

Exceções personalizadas

Para projetos maiores, criar suas próprias exceções melhora significativamente a clareza do código:

class ErroAplicacao(Exception):
    """Exceção base para erros 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 do usuário."""
    def __init__(self, mensagem="Credenciais inválidas"):
        self.mensagem = mensagem
        super().__init__(mensagem)

class ErroPermissao(ErroAplicacao):
    """Erro de permissão insuficiente."""
    def __init__(self, recurso, acao):
        self.recurso = recurso
        self.acao = acao
        super().__init__(f"Sem permissão para {acao} o recurso '{recurso}'")

# Usando exceções personalizadas
def validar_email(email):
    if not email or "@" not in email:
        raise ErroValidacao("email", "Formato de e-mail inválido")
    return True

def autenticar(usuario, senha):
    usuarios_validos = {"admin": "senha123"}
    if usuario not in usuarios_validos or usuarios_validos[usuario] != senha:
        raise ErroAutenticacao()
    return True

try:
    validar_email("usuario_sem_arroba")
except ErroValidacao as e:
    print(f"Campo: {e.campo}")
    print(f"Mensagem: {e.mensagem}")

try:
    autenticar("admin", "senha_errada")
except ErroAutenticacao as e:
    print(f"Falha na autenticação: {e.mensagem}")

Criar uma hierarquia de exceções permite capturar erros em diferentes níveis de granularidade. Capturar ErroAplicacao pega todos os erros da sua aplicação, enquanto ErroValidacao captura apenas erros de validação.

Padrão de contexto seguro

O gerenciador de contexto (with) é a maneira mais segura de lidar com recursos que precisam ser liberados:

class ConexaoBanco:
    """Simula uma conexão com banco de dados."""

    def __init__(self, nome_banco):
        self.nome_banco = nome_banco
        self.conectado = False

    def __enter__(self):
        self.conectado = True
        print(f"Conectado ao banco '{self.nome_banco}'")
        return self

    def __exit__(self, tipo_exc, valor_exc, traceback):
        self.conectado = False
        print(f"Desconectado do banco '{self.nome_banco}'")
        if tipo_exc is not None:
            print(f"Erro durante operação: {valor_exc}")
        return False  # Não suprime a exceção

    def executar(self, query):
        if not self.conectado:
            raise RuntimeError("Não conectado ao banco.")
        print(f"Executando: {query}")

# Uso seguro com with
with ConexaoBanco("meu_banco") as db:
    db.executar("SELECT * FROM usuarios")
    db.executar("UPDATE config SET valor = 'novo'")
# A conexão é fechada automaticamente, mesmo se houver erro

Logging de exceções

Em produção, imprimir erros no console não é suficiente. Use o módulo logging para registrar exceções de forma adequada:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)

def processar_pedido(pedido_id):
    """Processa um pedido com logging adequado."""
    logger.info(f"Iniciando processamento do pedido {pedido_id}")
    try:
        # Simulação de processamento
        if pedido_id <= 0:
            raise ValueError("ID do pedido deve ser positivo")
        logger.info(f"Pedido {pedido_id} processado com sucesso")
        return True
    except ValueError as e:
        logger.warning(f"Dado inválido no pedido {pedido_id}: {e}")
        return False
    except Exception as e:
        logger.exception(f"Erro inesperado ao processar pedido {pedido_id}")
        return False

processar_pedido(42)
processar_pedido(-1)

O método logger.exception() registra automaticamente o traceback completo, o que é essencial para depuração.

Boas práticas no tratamento de erros

Para escrever código robusto e profissional:

  • Seja específico: capture exceções específicas, não Exception genérico.
  • Não silencie erros: evite blocos except vazios com pass. Se capturou, faça algo com o erro.
  • Use else e finally: o bloco else mantém o try enxuto, e o finally garante limpeza de recursos.
  • Crie exceções personalizadas: para projetos médios e grandes, exceções customizadas melhoram a clareza.
  • Documente exceções: nas docstrings das funções, liste as exceções que podem ser levantadas.
  • Registre erros: use logging em vez de print para produção.
  • Falhe cedo: valide dados na entrada, não no meio do processamento.

Conclusão

O tratamento de erros é uma habilidade fundamental que separa código amador de código profissional. Python oferece ferramentas poderosas para lidar com exceções de forma elegante e eficiente. Dominar try/except, criar exceções personalizadas e seguir boas práticas de logging garantem que suas aplicações sejam robustas e fáceis de manter.

Como próximos passos, estude o módulo logging em profundidade, explore decoradores para tratamento de erros reutilizável e aprenda sobre validação de dados com bibliotecas como Pydantic, que automatizam grande parte da validação em projetos modernos.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português