7 Padrões de Tratamento de Erros em Python que Todo Desenvolvedor Deve Conhecer

O Python é fácil de escrever, mas um tratamento de erros descuidado pode transformar uma aplicação funcional em uma que falha silenciosamente em produção. Sem stack trace, sem alerta — apenas uma funcionalidade que parou de funcionar há três dias e ninguém percebeu.

Este artigo cobre 7 padrões de tratamento de erros que você realmente usará no trabalho. Todo snippet roda em Python 3.6+ puro, sem dependências.

Uma coisa a ter em mente antes de começar: tratamento de exceções não é algo que você adiciona no final. É parte do design. O melhor código não previne erros de acontecer — garante que os erros não derrubem o sistema.

1. try / except básico — Comece aqui

A base de todo tratamento de erros em Python. Capture tipos específicos de exceção e responda a cada um de forma diferente.

Python
def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return "Cannot divide by zero"
    except TypeError:
        return "Please provide numbers"

print(divide(10, 2))    # 5.0
print(divide(10, 0))    # Cannot divide by zero
print(divide(10, "a"))  # Please provide numbers

A regra de ouro: nunca escreva um except: vazio. Sempre nomeie o tipo de exceção. Um except vazio captura tudo — incluindo KeyboardInterrupt e SystemExit — e torna o debug praticamente impossível.

O erro clássico de iniciante:

Python
# NÃO faça isso
except Exception:
    pass

Isso engole todo erro silenciosamente. Quando algo quebrar três semanas depois, você terá zero pistas do que deu errado. No mínimo, registre a exceção:

Python
except ZeroDivisionError as e:
    logging.error("Division failed: %s", e)

Conclusão: Capture exceções por tipo. Trate except: pass como uma bomba-relógio.

2. finally — Limpeza Garantida

Quando você abre um arquivo, uma conexão de banco de dados ou um socket de rede, precisa fechá-lo — quer a operação tenha sucesso ou não. É para isso que serve o finally.

Python
f = None
try:
    f = open("data.txt", "w")
    f.write("hello")
except IOError as e:
    print("Write failed:", e)
finally:
    if f:
        f.close()
    print("Cleanup done")

O bloco finally roda não importa o que aconteça — mesmo se uma exceção for lançada, mesmo se você fizer return de dentro do bloco try.

3. A Instrução with — Como os Profissionais Fazem

Na prática, você raramente escreverá finally: f.close() à mão. A instrução with cuida disso automaticamente — e de forma mais limpa.

Python
try:
    with open("data.txt", "w") as f:
        f.write("hello")
except IOError as e:
    print("Write failed:", e)

A instrução with garante que o arquivo seja fechado quando o bloco termina, mesmo se ocorrer uma exceção. Funciona com qualquer objeto que implemente o protocolo de gerenciador de contexto.

Você pode abrir múltiplos arquivos em uma instrução:

Python
with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())

4. Re-lançando Exceções com raise

Às vezes você precisa registrar um erro e deixá-lo propagar. Um raise simples dentro de um bloco except re-lança a exceção original com seu traceback completo intacto.

Python
import logging

def process(data):
    try:
        return transform(data)
    except Exception as e:
        logging.error("Processing failed: %s", e)
        raise

Sem o raise, a função retorna None silenciosamente, e o chamador assume que tudo funcionou. Se quiser envolver o erro original com contexto adicional, use encadeamento de exceções:

Python
class ProcessingError(Exception):
    pass

try:
    result = transform(data)
except ValueError as e:
    raise ProcessingError("Bad input data") from e

5. Exceções Personalizadas — Erros que Significam Algo

Exceções embutidas são genéricas. Em uma aplicação real, ValueError poderia significar cem coisas diferentes. Exceções personalizadas tornam seu código auto-documentado.

Python
class ValidationError(Exception):
    """Lançada quando dados de entrada falham na validação."""
    pass

class APIError(Exception):
    """Lançada quando uma chamada a API externa falha."""
    def __init__(self, status_code, message):
        self.status_code = status_code
        super().__init__(f"{status_code}: {message}")

def validate_age(age):
    if age < 0:
        raise ValidationError("A idade não pode ser negativa")
    return age

try:
    validate_age(-5)
except ValidationError as e:
    print(e)  # A idade não pode ser negativa

6. assert — Verificações Apenas para Desenvolvimento

assert é uma forma rápida de verificar suposições durante o desenvolvimento. Se a condição for falsa, lança AssertionError.

Python
def withdraw(balance, amount):
    assert amount > 0, "Amount must be positive"
    assert balance >= amount, "Insufficient funds"
    return balance - amount

print(withdraw(100, 50))  # 50
print(withdraw(100, 200)) # AssertionError

O crítico sobre assert: ele pode ser desabilitado. Executar python -O (modo otimizado) remove todas as instruções assert. Para validação em produção, use verificações explícitas:

Python
def withdraw(balance, amount):
    if amount <= 0:
        raise ValueError("Valor deve ser positivo")
    if balance < amount:
        raise ValueError("Saldo insuficiente")
    return balance - amount

7. Lógica de Retry — Porque Redes Falham

Chamadas de API expiram. Conexões de banco caem. Lookups de DNS falham. Em qualquer sistema que fale com serviços externos, retry não é opcional.

Python
import time
import random

def call_api():
    if random.random() < 0.7:
        raise ConnectionError("Servidor indisponível")
    return {"status": "ok"}

max_retries = 5
for attempt in range(max_retries):
    try:
        result = call_api()
        print("Sucesso:", result)
        break
    except ConnectionError as e:
        wait = 2 ** attempt  # backoff exponencial
        print(f"Tentativa {attempt + 1} falhou, retentando em {wait}s...")
        time.sleep(wait)
else:
    print("Todas as tentativas esgotadas")

Pontos-chave para lógica de retry em produção: sempre defina um número máximo de retries; use backoff exponencial; só retente em erros transitórios.

Resumo: Tratamento de Erros é Design

1. try/except — Capture exceções específicas, nunca except vazio.
2. finally — Limpeza garantida de recursos.
3. with — A forma pythônica de gerenciar recursos.
4. raise — Registre e re-lance, não engula erros.
5. Exceções personalizadas — Tipos de erro auto-documentados.
6. assert — Apenas verificações em desenvolvimento.
7. Retry — Obrigatório para qualquer coisa envolvendo rede.

Os desenvolvedores acordados às 3h da manhã não são os que escrevem código esperto. São os que esqueceram de tratar o caso de erro. Tratamento de exceções não é sobre prevenir erros — é sobre garantir que os erros não derrubem o sistema.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *