¿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") # CRITICALTé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.pyTé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 sugerenciasflake8 - 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.pyEstrategias 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 paso33. 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 listaHerramientas Adicionales
ipdb - Debugger Mejorado
pip install ipdbimport ipdb
def mi_funcion():
x = 10
ipdb.set_trace() # Debugger con autocompletado y colores
y = x * 2
return yicecream - Debugging Elegante
pip install icecreamfrom 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.