Debugging com pdb e ferramentas modernas

1. Introdução ao pdb: o debugger embutido do Python

O pdb (Python Debugger) é a ferramenta de depuração padrão que acompanha toda instalação do Python. Desde a versão 3.7, o Python introduziu a função embutida breakpoint(), que simplifica significativamente a ativação do debugger.

Para ativar o pdb, existem duas abordagens principais:

# Abordagem 1: Executar o script diretamente com pdb
# python -m pdb meu_script.py

# Abordagem 2 (Python 3.7+): Inserir breakpoint() no código
def calcular_media(numeros):
    total = sum(numeros)
    breakpoint()  # Pausa a execução aqui
    return total / len(numeros)

resultado = calcular_media([10, 20, 30, 40])

Uma vez dentro do pdb, você precisa dominar alguns comandos essenciais:

# Exemplo prático de uso dos comandos
def processar_dados(lista):
    resultado = []
    for i, item in enumerate(lista):
        resultado.append(item * 2)
    return resultado

# Ao encontrar um breakpoint, use:
# (Pdb) n  - Avança para a próxima linha
# (Pdb) s  - Entra dentro de uma função
# (Pdb) c  - Continua até o próximo breakpoint
# (Pdb) q  - Sai do debugger

Para inspecionar variáveis, o pdb oferece comandos poderosos:

def calcular_desconto(preco, desconto):
    valor_desconto = preco * (desconto / 100)
    preco_final = preco - valor_desconto
    breakpoint()
    return preco_final

# Durante a depuração:
# (Pdb) p preco          # Imprime o valor de preco
# (Pdb) pp locals()      # Pretty print de todas as variáveis locais
# (Pdb) whatis desconto  # Mostra o tipo da variável
# (Pdb) p preco > 100    # Avalia expressões condicionais

2. Configurando breakpoints e navegação no código

Breakpoints estáticos são fundamentais para controlar onde a execução deve parar:

def calcular_fatorial(n):
    if n <= 1:
        return 1
    resultado = n * calcular_fatorial(n - 1)
    return resultado

# Exemplo de breakpoints no pdb:
# (Pdb) b 5              # Breakpoint na linha 5
# (Pdb) b calcular_fatorial  # Breakpoint na entrada da função

Breakpoints condicionais permitem pausar apenas quando uma condição específica é satisfeita:

def buscar_usuario(usuarios, id_alvo):
    for i, usuario in enumerate(usuarios):
        # No pdb: b 4, usuario['id'] == id_alvo
        if usuario['id'] == id_alvo:
            return usuario
    return None

# (Pdb) b 4, usuario['id'] == 42  # Pausa apenas quando id == 42

Gerenciar múltiplos breakpoints é essencial em scripts complexos:

# Comandos de gerenciamento:
# (Pdb) b                # Lista todos os breakpoints
# (Pdb) cl 1             # Limpa o breakpoint 1
# (Pdb) disable 2        # Desabilita temporariamente o breakpoint 2
# (Pdb) enable 2         # Reabilita o breakpoint 2
# (Pdb) ignore 1 5       # Ignora o breakpoint 1 nas próximas 5 execuções

3. Pós-mortem e debugging remoto com pdb

O debugging pós-mortem é uma técnica valiosa para analisar exceções após sua ocorrência:

# Execução com pós-mortem automático
# python -m pdb -c continue script_que_falha.py

import pdb

def dividir_numeros(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        pdb.post_mortem()  # Inicia pós-mortem interativo
        # Agora podemos inspecionar o estado no momento da exceção

O comando pm é particularmente útil para análise pós-exceção:

def processar_arquivo(caminho):
    with open(caminho, 'r') as arquivo:
        dados = arquivo.read()
        return eval(dados)  # Isso pode lançar uma exceção

# Após uma exceção não tratada:
# >>> import pdb
# >>> pdb.pm()  # Inicia debugging no frame da exceção

Para debugging remoto, podemos utilizar sockets:

import pdb
import socket

def iniciar_debug_remoto():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost', 4444))
    sock.listen(1)
    print("Aguardando conexão do debugger remoto...")
    conn, addr = sock.accept()

    # Redireciona stdin/stdout para o socket
    import sys
    sys.stdin = conn.makefile('r')
    sys.stdout = conn.makefile('w')

    pdb.set_trace()  # Inicia sessão remota

4. Ferramentas modernas: ipdb e pudb

O ipdb oferece uma experiência superior ao pdb tradicional com integração IPython:

# Instalação: pip install ipdb
import ipdb

def analisar_dados(series):
    ipdb.set_trace()  # Interface com autocomplete e syntax highlighting
    media = sum(series) / len(series)
    variancia = sum((x - media) ** 2 for x in series) / len(series)
    return media, variancia

# Recursos exclusivos do ipdb:
# - Tab completion para nomes de variáveis
# - Syntax highlighting colorido
# - Histórico de comandos persistente
# - Suporte a magias IPython como %timeit

O pudb oferece uma interface TUI (Text User Interface) completa:

# Instalação: pip install pudb
import pudb

def processar_transacoes(transacoes):
    pudb.set_trace()  # Abre interface TUI completa
    saldo = 0
    for transacao in transacoes:
        saldo += transacao['valor']
        if saldo < 0:
            print(f"Alerta: saldo negativo: {saldo}")
    return saldo

# Características do pudb:
# - Visualização dividida: código, variáveis, stack
# - Navegação por teclas de atalho
# - Exploração interativa de estruturas de dados

Comparação prática:
- Use pdb quando precisar de debugging básico sem dependências externas
- Use ipdb para desenvolvimento local com necessidade de autocomplete e análise rápida
- Use pudb para debugging visual em terminais, especialmente útil em servidores remotos

5. Debugging visual com IDEs e editores

VS Code

O VS Code oferece debugging visual integrado com suporte nativo a Python:

# Configuração de breakpoints via interface gráfica
def calcular_estatisticas(dados):
    media = sum(dados) / len(dados)
    # Clique na margem esquerda para adicionar breakpoint visual
    variancia = sum((x - media) ** 2 for x in dados) / len(dados)
    desvio = variancia ** 0.5
    return {'media': media, 'desvio': desvio}

# Recursos do VS Code:
# - Watch expressions para monitorar variáveis
# - Call stack visual com navegação entre frames
# - Debug Console para expressões arbitrárias
# - Logpoints que registram mensagens sem pausar execução

PyCharm

O PyCharm oferece um debugger gráfico avançado:

# PyCharm suporta step filtering para ignorar código de bibliotecas
import pandas as pd

def analisar_dataframe(df):
    # Breakpoint condicional: clique direito no breakpoint
    resultado = df.groupby('categoria').agg({
        'valor': ['sum', 'mean', 'count']
    })
    return resultado

# Diferenciais do PyCharm:
# - Evaluate expression com preview de tipos
# - Set Value para modificar variáveis durante debugging
# - Frame stepping automático
# - Breakpoints condicionais com expressões complexas

6. Debugging avançado com bibliotecas especializadas

O módulo faulthandler é essencial para capturar crashes em produção:

import faulthandler
import signal

# Ativa o faulthandler para capturar segfaults
faulthandler.enable()
faulthandler.register(signal.SIGUSR1)  # Registra sinal personalizado

def operacao_perigosa():
    import ctypes
    # Simula um crash que seria capturado pelo faulthandler
    ctypes.string_at(0)  # Isso causaria um segfault

O módulo trace permite rastreamento linha a linha:

import trace

def rastrear_execucao():
    tracer = trace.Trace(count=True, trace=True)
    tracer.run('print("Olá mundo")')

    # Gera relatório de cobertura
    resultados = tracer.results()
    resultados.write_results(show_missing=True, coverdir=".")

# Útil para:
# - Análise de cobertura de código
# - Rastreamento de fluxo de execução
# - Identificação de código morto

O icecream revoluciona o debugging com logs instantâneos:

# Instalação: pip install icecream
from icecream import ic

def calcular_comissao(vendas, taxa):
    ic()  # Loga localização e timestamp
    total_vendas = sum(vendas)
    ic(total_vendas)  # Mostra: ic| total_vendas: 15000

    comissao = total_vendas * taxa
    ic(comissao)  # ic| comissao: 750.0

    # ic() retorna o valor, permitindo uso inline
    resultado = ic(comissao * 0.8)  # ic| comissao * 0.8: 600.0
    return resultado

# Recursos do icecream:
# - Cores para melhor visualização
# - Contexto automático (arquivo, linha, função)
# - Prefixo personalizável
# - Fácil desativação global

7. Boas práticas e armadilhas comuns

Evite breakpoints em produção usando variáveis de ambiente:

import os

# Em desenvolvimento:
# PYTHONBREAKPOINT=pdb.set_trace

# Em produção:
os.environ['PYTHONBREAKPOINT'] = '0'

# Isso desativa todos os breakpoint() sem modificar o código
def funcao_critica():
    breakpoint()  # Será ignorado em produção
    # ... lógica importante

Debugging de código multithread requer atenção especial:

import threading
import pdb

def worker_thread(id):
    try:
        # pdb não lida bem com múltiplas threads
        # Cada thread pode pausar em breakpoints diferentes
        resultado = processar_dados(id)
        return resultado
    except Exception as e:
        # Melhor prática: logging em vez de pdb em threads
        import logging
        logging.error(f"Thread {id} falhou: {e}")

# Alternativa: usar logging com tracebacks completos
import traceback

def worker_seguro(id):
    try:
        return processar_dados(id)
    except:
        traceback.print_exc()  # Imprime stack trace completo

Para debugging de bibliotecas C e pacotes externos:

# Estratégias para debugging de código nativo:
# 1. Use gdb para debugging de extensões C
# 2. Configure PYTHONFAULTHANDLER para capturar segfaults
# 3. Use ctypes.util.find_library() para localizar bibliotecas

import ctypes
import os

# Habilita faulthandler para capturar crashes em C
os.environ['PYTHONFAULTHANDLER'] = '1'

def chamar_biblioteca_externa():
    try:
        lib = ctypes.CDLL('./minha_biblioteca.so')
        lib.funcao_perigosa()
    except Exception as e:
        print(f"Erro ao chamar biblioteca: {e}")

8. Conclusão: montando seu arsenal de debugging

Montar um arsenal eficiente de debugging significa escolher a ferramenta certa para cada cenário:

Fluxo recomendado:
1. Desenvolvimento local: ipdb ou IDE (VS Code/PyCharm) para conforto visual
2. Análise rápida: icecream com ic() para logs instantâneos
3. Produção: faulthandler + logging estruturado
4. Servidores remotos: pudb para debugging visual via terminal
5. CI/CD: pdb com scripts automatizados

Checklist de ferramentas por cenário:

Cenário Ferramenta Recomendada
Desenvolvimento IDE + ipdb
Produção faulthandler + logging
Análise pós-exceção pdb.pm()
Código multithread logging + traceback
Bibliotecas C gdb + faulthandler
CI/CD pdb scriptado

Próximos passos: Combine debugging com profiling (cProfile) para identificar gargalos de performance, e logging estruturado (structlog) para monitoramento contínuo em produção. Essas técnicas complementares transformam debugging de uma atividade reativa em uma prática proativa de qualidade de código.

Referências