Debugging en Python: Técnicas para Encontrar y Corregir Errores

👤 Admin 📅 21 de octubre, 2025 ⏱ 18 min 🏷 Python Avanzado

¿Qué es el Debugging?

El debugging (depuración) es el proceso de identificar, analizar y corregir errores o bugs en el código. Es una habilidad fundamental que todo programador debe dominar para crear software confiable y eficiente.

Tipos de Errores en Python

1. Errores de Sintaxis (SyntaxError)

# Error: falta dos puntos
if x > 5
    print("Mayor")
# SyntaxError: invalid syntax

# Correcto
if x > 5:
    print("Mayor")

# Error: paréntesis sin cerrar
print("Hola"
# SyntaxError: unexpected EOF

# Correcto
print("Hola")

2. Errores de Indentación (IndentationError)

# Error: indentación incorrecta
def saludar():
print("Hola")
# IndentationError: expected an indented block

# Correcto
def saludar():
    print("Hola")

3. Errores de Nombre (NameError)

# Error: variable no definida
print(nombre)
# NameError: name 'nombre' is not defined

# Correcto
nombre = "Ana"
print(nombre)

4. Errores de Tipo (TypeError)

# Error: operación entre tipos incompatibles
resultado = "5" + 5
# TypeError: can only concatenate str (not "int") to str

# Correcto
resultado = int("5") + 5  # 10
# o
resultado = "5" + str(5)  # "55"

5. Errores de Índice (IndexError)

# Error: índice fuera de rango
lista = [1, 2, 3]
print(lista[5])
# IndexError: list index out of range

# Correcto: verificar longitud
if len(lista) > 5:
    print(lista[5])
else:
    print("Índice fuera de rango")

6. Errores de Clave (KeyError)

# Error: clave no existe
diccionario = {"nombre": "Ana", "edad": 25}
print(diccionario["ciudad"])
# KeyError: 'ciudad'

# Correcto: usar get()
print(diccionario.get("ciudad", "No especificada"))

7. Errores de Valor (ValueError)

# Error: conversión inválida
numero = int("abc")
# ValueError: invalid literal for int()

# Correcto: validar entrada
texto = "abc"
if texto.isdigit():
    numero = int(texto)
else:
    print("Entrada inválida")

8. Errores de Atributo (AttributeError)

# Error: método no existe
lista = [1, 2, 3]
lista.append_all([4, 5])  # No existe este método
# AttributeError: 'list' object has no attribute 'append_all'

# Correcto: usar extend()
lista.extend([4, 5])

Técnica 1: Print Debugging

# Método más simple: usar print()
def calcular_promedio(numeros):
    print(f"DEBUG: numeros = {numeros}")  # Ver qué se recibe
    
    total = sum(numeros)
    print(f"DEBUG: total = {total}")  # Ver suma
    
    cantidad = len(numeros)
    print(f"DEBUG: cantidad = {cantidad}")  # Ver cantidad
    
    promedio = total / cantidad
    print(f"DEBUG: promedio = {promedio}")  # Ver resultado
    
    return promedio

resultado = calcular_promedio([10, 20, 30])
print(f"Resultado final: {resultado}")

Print con Información del Sistema

import sys
import os

def debug_info():
    print("=== DEBUG INFO ===")
    print(f"Python version: {sys.version}")
    print(f"Current directory: {os.getcwd()}")
    print(f"Script: {__file__}")
    print(f"Platform: {sys.platform}")
    print("==================")

debug_info()

Técnica 2: Python Debugger (pdb)

Uso Básico de pdb

import pdb

def dividir(a, b):
    pdb.set_trace()  # Punto de interrupción
    resultado = a / b
    return resultado

# Cuando llegue aquí, el programa se pausará
print(dividir(10, 2))

Comandos de pdb

# Comandos disponibles en pdb:
# n (next) - Ejecutar siguiente línea
# s (step) - Entrar en función
# c (continue) - Continuar hasta siguiente breakpoint
# l (list) - Mostrar código actual
# p variable - Imprimir valor de variable
# pp variable - Pretty print
# w (where) - Mostrar stack trace
# q (quit) - Salir del debugger
# h (help) - Ayuda
# b línea - Establecer breakpoint

# Ejemplo de uso
import pdb

def procesar_lista(lista):
    resultado = []
    for i, item in enumerate(lista):
        pdb.set_trace()  # Pausar en cada iteración
        procesado = item * 2
        resultado.append(procesado)
    return resultado

procesar_lista([1, 2, 3])

breakpoint() - Python 3.7+

# Forma moderna (Python 3.7+)
def calcular(x, y):
    breakpoint()  # Equivalente a pdb.set_trace()
    resultado = x + y
    return resultado

calcular(5, 3)

Técnica 3: Logging

import logging

# Configurar logging
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('debug.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

def procesar_datos(datos):
    logger.debug(f"Iniciando procesamiento con: {datos}")
    
    try:
        resultado = [x * 2 for x in datos]
        logger.info(f"Procesamiento exitoso: {len(resultado)} items")
        return resultado
    
    except Exception as e:
        logger.error(f"Error en procesamiento: {e}")
        logger.exception("Stack trace completo:")  # Incluye traceback
        return []

procesar_datos([1, 2, 3])

Niveles de Logging

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# Niveles (de menor a mayor importancia)
logger.debug("Información detallada para debugging")      # DEBUG
logger.info("Información general del flujo")              # INFO
logger.warning("Advertencia, algo puede estar mal")       # WARNING
logger.error("Error, algo falló")                         # ERROR
logger.critical("Error crítico, el programa puede fallar") # CRITICAL

Técnica 4: Assertions

# Usar assert para verificar condiciones
def dividir(a, b):
    assert b != 0, "El divisor no puede ser cero"
    assert isinstance(a, (int, float)), "a debe ser número"
    assert isinstance(b, (int, float)), "b debe ser número"
    
    return a / b

# Si la condición es False, lanza AssertionError
try:
    resultado = dividir(10, 0)
except AssertionError as e:
    print(f"Error de aserción: {e}")

# Desactivar assertions en producción
# python -O script.py

Técnica 5: Try-Except Detallado

def leer_archivo(nombre_archivo):
    try:
        with open(nombre_archivo, 'r') as f:
            contenido = f.read()
            return contenido
    
    except FileNotFoundError:
        print(f"Error: Archivo '{nombre_archivo}' no encontrado")
        return None
    
    except PermissionError:
        print(f"Error: Sin permisos para leer '{nombre_archivo}'")
        return None
    
    except Exception as e:
        print(f"Error inesperado: {type(e).__name__}: {e}")
        import traceback
        traceback.print_exc()  # Imprimir stack trace completo
        return None

contenido = leer_archivo("datos.txt")

Técnica 6: Traceback

import traceback
import sys

def funcion_a():
    funcion_b()

def funcion_b():
    funcion_c()

def funcion_c():
    # Provocar error
    resultado = 10 / 0

try:
    funcion_a()
except Exception as e:
    print("Error capturado:")
    print(f"Tipo: {type(e).__name__}")
    print(f"Mensaje: {e}")
    print("\nStack trace:")
    traceback.print_exc()
    
    # Obtener información detallada
    exc_type, exc_value, exc_traceback = sys.exc_info()
    print("\nInformación detallada:")
    print(f"Tipo de excepción: {exc_type}")
    print(f"Valor: {exc_value}")
    print(f"Archivo: {exc_traceback.tb_frame.f_code.co_filename}")
    print(f"Línea: {exc_traceback.tb_lineno}")

Técnica 7: Debugging con VS Code

launch.json

// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Archivo actual",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "justMyCode": true
        },
        {
            "name": "Python: Flask",
            "type": "python",
            "request": "launch",
            "module": "flask",
            "env": {
                "FLASK_APP": "app.py",
                "FLASK_DEBUG": "1"
            },
            "args": [
                "run",
                "--no-debugger",
                "--no-reload"
            ],
            "jinja": true
        }
    ]
}

Técnica 8: Herramientas de Análisis

pylint - Análisis de Código

# Instalar
pip install pylint

# Analizar archivo
pylint mi_script.py

# Genera reporte con errores y sugerencias

flake8 - Linter

# Instalar
pip install flake8

# Analizar
flake8 mi_script.py

# Analizar directorio
flake8 .

black - Formateador

# Instalar
pip install black

# Formatear archivo
black mi_script.py

# Ver cambios sin aplicar
black --check mi_script.py

Estrategias de Debugging

1. Reproducir el Error

# Crear caso de prueba mínimo
def test_error():
    # Reducir código al mínimo que reproduce el error
    x = None
    resultado = x + 5  # Error aquí
    return resultado

test_error()

2. Dividir y Conquistar

# Si una función larga tiene error, dividirla
def procesar_datos_complejo(datos):
    # Separar en pasos
    paso1 = limpiar_datos(datos)
    print(f"Paso 1 OK: {len(paso1)} items")
    
    paso2 = validar_datos(paso1)
    print(f"Paso 2 OK: {len(paso2)} items")
    
    paso3 = transformar_datos(paso2)
    print(f"Paso 3 OK: {len(paso3)} items")
    
    return paso3

3. Rubber Duck Debugging

Explica tu código línea por línea (a un patito de goma o a ti mismo). Frecuentemente encontrarás el error al explicarlo.

Debugging de Problemas Comunes

Problema: Variable None

# Problema
def obtener_usuario(id):
    usuarios = {1: "Ana", 2: "Luis"}
    return usuarios.get(id)  # Retorna None si no existe

usuario = obtener_usuario(5)
print(usuario.upper())  # AttributeError: 'NoneType' has no attribute 'upper'

# Solución: Verificar None
usuario = obtener_usuario(5)
if usuario is not None:
    print(usuario.upper())
else:
    print("Usuario no encontrado")

Problema: Lista Modificándose Durante Iteración

# Problema
lista = [1, 2, 3, 4, 5]
for item in lista:
    if item % 2 == 0:
        lista.remove(item)  # ¡Modifica lista durante iteración!
print(lista)  # Resultado inesperado

# Solución 1: Iterar sobre copia
lista = [1, 2, 3, 4, 5]
for item in lista.copy():
    if item % 2 == 0:
        lista.remove(item)

# Solución 2: List comprehension
lista = [1, 2, 3, 4, 5]
lista = [item for item in lista if item % 2 != 0]

Problema: Variables Mutables como Argumentos

# Problema
def agregar_item(item, lista=[]):
    lista.append(item)
    return lista

print(agregar_item(1))  # [1]
print(agregar_item(2))  # [1, 2] ¡Sorpresa!

# Solución: Usar None como default
def agregar_item(item, lista=None):
    if lista is None:
        lista = []
    lista.append(item)
    return lista

Herramientas Adicionales

ipdb - Debugger Mejorado

pip install ipdb
import ipdb

def mi_funcion():
    x = 10
    ipdb.set_trace()  # Debugger con autocompletado y colores
    y = x * 2
    return y

icecream - Debugging Elegante

pip install icecream
from icecream import ic

def calcular(x, y):
    ic(x, y)  # Muestra: ic| x: 5, y: 3
    resultado = x + y
    ic(resultado)  # Muestra: ic| resultado: 8
    return resultado

calcular(5, 3)

Checklist de Debugging

  • ✅ Lee el mensaje de error completo
  • ✅ Identifica la línea exacta del error
  • ✅ Verifica tipos de variables (print(type(variable)))
  • ✅ Revisa valores de variables en puntos clave
  • ✅ Simplifica el código al mínimo reproducible
  • ✅ Verifica condiciones de entrada/salida
  • ✅ Usa debugger para paso a paso
  • ✅ Busca el error en Google/Stack Overflow
  • ✅ Toma un descanso si estás atascado

Conclusión

El debugging es una habilidad que mejora con la práctica. Combina diferentes técnicas según el problema: print() para casos simples, pdb para análisis detallado, logging para aplicaciones en producción, y herramientas como pylint para prevenir errores. Recuerda que encontrar y corregir bugs es una parte normal del desarrollo, no te frustres.