Saltar a contenido

2.4.-Depurar programas

2.4. Depuración de programas

Idea principal

Depurar es investigar el comportamiento de un programa para localizar la causa de un problema y corregirlo. El depurador permite detener la ejecución, avanzar paso a paso e inspeccionar las variables y la pila de llamadas.

Un programa puede terminar con una excepción, producir una salida incorrecta o quedarse ejecutándose indefinidamente. En los tres casos necesitamos obtener evidencias sobre lo que está ocurriendo, en lugar de cambiar instrucciones al azar. La depuración (debugging) proporciona herramientas para observar el estado del programa mientras se ejecuta.

La depuración no consiste solo en poner print() por todas partes. Los mensajes temporales pueden ayudar en un caso sencillo, pero un depurador permite detenerse en un punto concreto, revisar el valor de una expresión y seguir la ejecución sin modificar el código. También conviene combinarlo con pruebas automatizadas, aserciones y registros (logging) según el entorno en el que se ejecute la aplicación.

El objetivo de este tema no es memorizar botones del entorno, sino entender el flujo de ejecución, formular hipótesis a partir de evidencias y saber comprobar que una corrección no ha introducido otro problema.

La normativa del módulo de Programación relaciona este tema con el siguiente resultado de aprendizaje y sus criterios de evaluación:

Código Descripción literal de la normativa
RA 3 Escribe y depura código, analizando y utilizando las estructuras de control del lenguaje.
CE 3.f Se han probado y depurado los programas.

Se trabajan los criterios que tienen que ver con la depuración de programas, la localización de errores y la verificación de correcciones.

Qué deberías saber al terminar

Al acabar este tema deberías poder:

  • distinguir errores de sintaxis, de ejecución y de lógica;
  • reproducir un problema y formular una hipótesis comprobable;
  • establecer puntos de interrupción y ejecutar un programa paso a paso;
  • inspeccionar variables, expresiones, la consola y la pila de llamadas;
  • utilizar el depurador de Visual Studio Code y reconocer pdb;
  • corregir un programa y verificarlo con casos normales y casos límite.

Mapa del tema

En este documento seguiremos esta progresión:

  1. qué significa depurar y qué tipos de problemas existen;
  2. proceso sistemático y herramientas de depuración;
  3. depuración paso a paso con Visual Studio Code;
  4. corrección y análisis de un programa Python;
  5. buenas prácticas, errores frecuentes y actividades.

1. Qué significa depurar

Cuando el resultado no coincide con lo esperado, la primera tarea es describir el comportamiento observable: qué datos se introdujeron, qué salida apareció, qué excepción se produjo o en qué punto dejó de avanzar. Después se busca la causa, se aplica una corrección pequeña y se vuelve a ejecutar el caso que fallaba.

Depuración

La depuración es el proceso sistemático de localizar, comprender y corregir defectos de un programa mediante la observación de su ejecución y de su estado interno.

La depuración puede ser necesaria aunque el programa no muestre ningún mensaje de error. Por ejemplo, una función puede devolver el máximo y el mínimo sin lanzar una excepción, pero utilizar datos fuera del intervalo que especificaba el enunciado. Python ejecuta el código, aunque el resultado sea incorrecto.

1.1. Tipos de problemas

Problema Cuándo aparece Ejemplo
Error de sintaxis Python no puede interpretar el código. range(0..10) o falta de :.
Error de ejecución Una operación no puede completarse durante la ejecución. Convertir "hola" a int.
Error de lógica El programa se ejecuta, pero decide o calcula mal. Usar or cuando se necesita and.
Error de configuración El entorno no tiene el intérprete o dependencia esperada. Seleccionar otro intérprete de Python.

Los errores de sintaxis deben corregirse antes de iniciar una sesión normal de depuración: si Python no puede analizar el archivo, no puede ejecutar sus instrucciones. Los errores de ejecución suelen aparecer como excepciones y los errores de lógica requieren comparar el estado y la salida con el resultado esperado.

Un programa que funciona, pero no cumple la especificación

Si el enunciado pide diez números aleatorios entre 1 y 100 y el programa utiliza random.randint(1, 1000), el programa se ejecuta correctamente, pero puede generar datos fuera del intervalo permitido. El depurador permite observar los valores reales y relacionarlos con la decisión incorrecta.

1.2. Un proceso de depuración reproducible

Depurar es más eficaz cuando se sigue un orden. El siguiente flujo evita confundir una hipótesis con un hecho:

flowchart TD
    A["Reproducir el problema<br/>con datos conocidos"] --> B["Observar la salida,<br/>la excepción o el bloqueo"]
    B --> C["Formular una hipótesis<br/>sobre la causa"]
    C --> D["Elegir un punto de interrupción<br/>y observar el estado"]
    D --> E["Ejecutar paso a paso<br/>e inspeccionar valores"]
    E --> F{"¿El estado es el esperado?"}
    F -->|No| G["Aislar la causa<br/>y corregirla"]
    G --> H["Repetir el caso<br/>que fallaba"]
    H --> E
    F -->|Sí| I["Probar otros casos<br/>y documentar la solución"]

Un caso reproducible es especialmente importante cuando intervienen entradas aleatorias, fechas, red o ficheros. Durante la investigación se puede fijar una semilla aleatoria, utilizar datos de prueba conocidos o guardar la entrada que provocó el fallo. La intención es que otra persona pueda repetir la observación.

2. Herramientas y conceptos del depurador

La herramienta adecuada depende del problema y del entorno. Estas opciones no son excluyentes:

Herramienta Para qué sirve Ejemplo
print() Observar rápidamente un valor intermedio. print("total:", total)
Depurador del IDE Detener, avanzar e inspeccionar el estado. F5, F10 y F11 en VS Code.
pdb Depurar desde la terminal con la biblioteca estándar. python -m pdb programa.py
logging Registrar información con niveles y destinos configurables. logger.info("Pedido procesado")
assert Comprobar invariantes durante el desarrollo. assert total >= 0
Pruebas Verificar automáticamente resultados esperados. pytest y casos límite.

Una aserción expresa una condición que el programa considera verdadera durante el desarrollo. Si la condición es falsa, Python lanza AssertionError y el depurador permite investigar el estado que la ha provocado:

def calcular_media(valores: list[float]) -> float:
    assert valores, "La lista no puede estar vacía durante el desarrollo."
    return sum(valores) / len(valores)

Las aserciones sirven para detectar invariantes y errores de programación, no para validar datos externos: Python puede desactivarlas con python -O. Para una entrada de una persona usuaria o de un servicio se debe utilizar una condición explícita y tratar el resultado con el mecanismo adecuado.

Depurar no es lo mismo que controlar una excepción

Un bloque try-except decide cómo responder a una excepción prevista; el depurador ayuda a investigar por qué se llegó a ese estado. Capturar una excepción sin comprenderla puede ocultar un error lógico. Consulta también control de excepciones.

2.1. pdb: el depurador de la biblioteca estándar

Python incluye el módulo pdb, que permite investigar un programa desde la terminal. También se puede introducir breakpoint() en una línea concreta para iniciar el depurador cuando la ejecución llegue a ella:

def dividir(total: float, cantidad: int) -> float:
    """Divide un total entre una cantidad positiva."""
    breakpoint()
    return total / cantidad


resultado = dividir(10, 2)
print(resultado)

Otra posibilidad es iniciar el programa directamente con pdb:

python -m pdb programa.py

Durante una sesión de pdb, algunos comandos habituales son:

Comando Acción
n Ejecuta la siguiente línea sin entrar en una función.
s Ejecuta la siguiente línea entrando en una función propia.
c Continúa hasta el siguiente punto de interrupción.
p expresión Muestra el valor de una expresión.
w Muestra la pila de llamadas.
q Termina la sesión de depuración.

La función incorporada breakpoint() es cómoda durante el desarrollo, pero no debe quedar olvidada en una entrega o en producción. Antes de usarla, conviene comprobar que el proyecto no desactiva los puntos de interrupción mediante la configuración de PYTHONBREAKPOINT.

3. Depurar con Visual Studio Code

Visual Studio Code puede depurar programas Python si está instalada la extensión Python de Microsoft y el editor utiliza el intérprete correcto. El nombre habitual del editor es Visual Studio Code, aunque en apuntes antiguos puede aparecer como «Visual Code».

3.1. Preparar el entorno

Antes de iniciar el depurador:

  1. Instala la extensión Python desde Extensions.
  2. Abre la carpeta del proyecto, no solo el archivo suelto si el proyecto tiene configuración propia.
  3. Selecciona el intérprete con Python: Select Interpreter.
  4. Abre el archivo .py que quieres ejecutar.
  5. Comprueba primero que el programa se puede ejecutar de forma normal.

Si el proyecto necesita argumentos, variables de entorno o un directorio de trabajo concreto, puedes configurar un perfil en .vscode/launch.json. Para los primeros ejemplos, el perfil que ofrece Run and Debug suele ser suficiente.

3.2. Puntos de interrupción y estado del programa

Un breakpoint, punto de interrupción o punto de parada detiene el flujo de ejecución justo antes de ejecutar la instrucción marcada. Se puede colocar haciendo clic en el margen izquierdo de una línea o con F9. El programa debe recorrer esa línea para que el punto se active; colocar un punto dentro de una rama que nunca se cumple no detendrá la ejecución.

Para comenzar, selecciona Run and Debug en la barra lateral y pulsa Start Debugging o F5. La línea resaltada indica la próxima instrucción que se ejecutará. En el panel Variables se muestran, entre otros datos, las variables locales del marco actual y las variables globales disponibles.

Punto de interrupción en Visual Studio Code
Un punto de interrupción detiene la ejecución en una línea concreta.
Panel de variables del depurador de Visual Studio Code
El panel Variables permite inspeccionar el estado local y global.

3.3. Ejecutar paso a paso

Las órdenes principales del depurador son:

Orden Atajo habitual Comportamiento
Continue F5 Continúa hasta el siguiente breakpoint o hasta terminar.
Step Over F10 Ejecuta la línea sin entrar en la función que llama.
Step Into F11 Ejecuta la línea y entra en una función propia si procede.
Step Out Mayús.+F11 Termina la función actual y vuelve a quien la llamó.
Stop Mayús.+F5 Detiene la sesión de depuración.

Step Into permite seguir una función línea a línea. Si solo quieres utilizar una función que ya has comprobado, Step Over la ejecuta completa y evita entrar en su implementación. Si has entrado demasiado, Step Out vuelve al marco que hizo la llamada.

Al pulsar Step Into o F11 en el ejemplo, se ejecuta la siguiente instrucción. Si acabamos de crear numeros = [], el panel Variables mostrará una lista vacía. Después, al recorrer el bucle, podremos ver cómo se añaden elementos.

Orden Step Into del depurador de Visual Studio Code
Step Into entra en una función propia para seguirla paso a paso.

3.4. Watch, Debug Console y Call Stack

El panel Watch permite añadir expresiones que queremos observar durante la sesión. Por ejemplo, len(numeros) muestra cómo cambia el tamaño de la lista y numeros muestra sus elementos. Las expresiones se evalúan con el contexto del marco seleccionado; una variable local puede no estar disponible después de salir de su función.

Panel Watch del depurador de Visual Studio Code
Watch permite seguir expresiones como len(numeros).

La Debug Console se abre normalmente en la parte inferior. Durante una parada podemos consultar expresiones Python usando los valores actuales del programa:

len(numeros)
numeros
1 in numeros

Estas expresiones ayudan a comprobar una hipótesis sin añadir un print() al código. Hay que recordar que una consola de depuración puede permitir también modificar el estado; para una investigación reproducible, anota cualquier modificación y vuelve a ejecutar desde el principio antes de validar el arreglo.

Consola de depuración de Visual Studio Code
La Debug Console permite consultar expresiones en el estado actual.

El panel Call Stack muestra la pila de llamadas: cada marco representa una función que está activa y permite entender qué función llamó a cuál. Es muy útil cuando una excepción aparece dentro de varias funciones anidadas. La línea que produce el error es el punto de detección; la causa puede estar en un valor calculado en un marco anterior.

4. Ejemplo completo: generar y buscar números

El ejemplo original de estos apuntes plantea una función que recibe una lista, calcula su máximo y su mínimo, genera diez números aleatorios entre 1 y 100 y comprueba si otro número está en la lista. Es un buen caso para depurar porque combina una función, un bucle, una validación de entrada y una búsqueda.

4.1. Código corregido para iniciar la depuración

El siguiente programa es ejecutable y conserva ese objetivo. La función calcular_max_min() necesita una lista no vacía; el bucle garantiza que la lista contenga diez elementos antes de llamarla.

#!/usr/bin/env python3
import random


def calcular_max_min(lista: list[int]) -> tuple[int, int]:
    """Devuelve el valor máximo y el mínimo de una lista no vacía."""
    return max(lista), min(lista)


def pedir_numero() -> int:
    """Pide un número entero comprendido entre 1 y 100."""
    valido = False
    numero = 0

    while not valido:
        texto = input("Dime un número del 1 al 100: ")

        try:
            numero = int(texto)
        except ValueError:
            print("Debes introducir un número entero.")
        else:
            if 1 <= numero <= 100:
                valido = True
            else:
                print("El número debe estar entre 1 y 100.")

    return numero


numeros = []

# Inicializamos la lista con diez valores aleatorios entre 1 y 100.
for _ in range(10):
    numeros.append(random.randint(1, 100))

vmax, vmin = calcular_max_min(numeros)
print("Lista generada:", numeros)
print(f"El valor máximo es {vmax}.")
print(f"El valor mínimo es {vmin}.")

numero = pedir_numero()

if numero in numeros:
    print("El número está en la lista.")
else:
    print("El número no está en la lista.")

Una ejecución concreta puede mostrar una lista distinta porque interviene la generación aleatoria. Para depurar una ejecución concreta, anota la lista generada o utiliza una semilla fija durante las pruebas. La aleatoriedad no es un error, pero dificulta comparar dos ejecuciones si no se controla.

4.2. Qué errores tenía el borrador inicial

El borrador se entrega como ejercicio de corrección y contiene errores intencionados. No se debe intentar depurar su flujo hasta corregir primero los errores que impiden analizarlo. Estos son los cambios importantes:

Fragmento o problema Tipo Corrección
range(0..10) Sintaxis range(10), porque el límite final es exclusivo.
randint(1, 1000) Lógica randint(1, 100), según el intervalo pedido.
Comillas tipográficas “ ” Sintaxis Comillas normales de Python, como "texto".
while numero > 0 or numero < 100 Lógica Validar numero < 1 or numero > 100, o usar not 1 <= numero <= 100.
Sangría ausente en el while y el if Sintaxis Indentar el cuerpo cuatro espacios.
if numero in numero Ejecución Buscar en la lista: if numero in numeros.
elsa Sintaxis La palabra reservada correcta es else.
CalcularMaxMin Estilo calcular_max_min sigue la convención habitual de Python.

La condición original numero > 0 or numero < 100 es especialmente importante: para cualquier entero una de las dos comparaciones es verdadera, por lo que la petición se repetiría indefinidamente. Para describir un número fuera del intervalo se necesita que sea menor que 1 o mayor que 100.

Primero sintaxis; después comportamiento

Un depurador no puede avanzar por una instrucción que Python no consigue interpretar. Corrige primero la sintaxis y los nombres inexistentes; después utiliza el depurador para investigar excepciones y errores de lógica.

4.3. Sesión de depuración paso a paso

Podemos seguir esta secuencia con el programa corregido:

  1. Coloca un breakpoint en numeros.append(...) y pulsa Start Debugging o F5.
  2. Comprueba en Variables que numeros comienza como una lista vacía y que la próxima instrucción es la línea resaltada.
  3. Pulsa F11 para ejecutar la siguiente instrucción. Al repetirlo, observa cómo la lista va incorporando números.
  4. Añade len(numeros) y numeros al panel Watch para seguir su tamaño y contenido.
  5. Cuando llegues a calcular_max_min(numeros), usa F10 si confías en la función o F11 si quieres entrar en ella y observar max() y min().
  6. Coloca un breakpoint en if numero in numeros y comprueba en la consola numero, numeros y numero in numeros.
  7. Pulsa F5 para continuar hasta el siguiente breakpoint o hasta el final. Si una llamada no produce el resultado esperado, revisa el Call Stack.

La línea resaltada representa la próxima instrucción, no una instrucción que ya se haya ejecutado. Esta diferencia explica muchos errores al interpretar una captura del depurador: antes de pulsar el botón, el estado corresponde al punto anterior a esa línea.

5. Buenas prácticas

Para depurar con criterio profesional, conviene aplicar estas pautas:

  • Reproduce el problema con una entrada concreta y conserva los datos relevantes antes de cambiar el código.
  • Formula una hipótesis y cambia una sola cosa cada vez; así sabrás qué modificación ha resuelto el problema.
  • Coloca breakpoints estratégicos en los cambios de estado, las decisiones y las llamadas a funciones, no en todas las líneas.
  • Inspecciona valores y tipos: un valor aparentemente correcto puede ser una cadena cuando el programa necesita un entero.
  • Usa Step Over y Step Into con intención: entra solo en el código que necesitas investigar y evita perderte en librerías ya conocidas.
  • Comprueba los límites: prueba listas vacías, un solo elemento, valores mínimos y máximos, entradas inválidas y funciones que no reciben datos.
  • Verifica la corrección con pruebas y no solo con una ejecución manual que haya terminado sin excepciones.
  • Retira print() y breakpoint() temporales antes de entregar el código; utiliza logging cuando necesites un registro controlado.
  • No expongas información sensible en salidas, registros o capturas de pantalla, especialmente si el programa maneja contraseñas o datos personales.

6. Errores frecuentes

Error frecuente Por qué ocurre Cómo evitarlo
Intentar iniciar el depurador con errores de sintaxis. Python no puede construir el programa ejecutable. Corrige primero la línea indicada por el editor o el intérprete.
Colocar un breakpoint en una rama que no se ejecuta. La condición nunca se cumple con los datos actuales. Usa datos que recorran esa rama o coloca un punto anterior.
Confundir la línea resaltada con la ya ejecutada. El depurador marca la próxima instrucción. Observa el estado antes y después de avanzar.
Entrar en todas las funciones con Step Into. Se pierde tiempo en librerías o código que no se investiga. Usa Step Over cuando la función ya esté comprobada.
Leer una variable fuera de su alcance. La variable local solo existe en su marco de llamada. Selecciona el marco adecuado en Call Stack o inspecciónala dentro de la función.
Corregir solo la excepción y no la causa. Se interpreta el mensaje como el problema completo. Revisa los valores y las decisiones que llevaron hasta la línea indicada.
Usar or y and sin analizar la condición. La expresión puede ser verdadera siempre o nunca. Prueba valores dentro, fuera y en los límites del intervalo.
Dejar un breakpoint() o un print() en la entrega. El código de investigación se mezcla con el código final. Haz una revisión final y ejecuta las pruebas sin instrumentación temporal.
Probar únicamente el camino correcto. Los datos ideales no cubren errores ni límites. Añade casos de excepción, extremos, vacío y valores inesperados.

7. Resumen

En este tema has aprendido que:

  • depurar es observar y razonar sobre el estado del programa, no cambiar código al azar;
  • los errores de sintaxis, ejecución, lógica y configuración se investigan de forma diferente;
  • un breakpoint permite detener la ejecución y Step Into, Step Over, Step Out y Continue permiten recorrerla con distinto nivel de detalle;
  • Variables, Watch, Debug Console y Call Stack muestran el estado y el camino que ha seguido la ejecución;
  • pdb, print(), logging, assert y las pruebas complementan al depurador;
  • toda corrección debe verificarse con el caso que fallaba y con casos límite.

Idea clave

Una buena depuración convierte un comportamiento inesperado en evidencias: se reproduce el problema, se observa el estado, se aísla la causa, se corrige con una modificación pequeña y se verifica el resultado.

8. Para seguir practicando

  • Realiza la práctica de depuración del algoritmo burbuja: establece un breakpoint, sigue los índices y observa cómo cambia la lista.
  • Captura en Variables el estado de una colección durante varias iteraciones y añade len(lista) al panel Watch.
  • Reproduce el ejemplo de los números, introduce una letra y explica el ValueError antes de decidir cómo tratarlo.
  • Ejecuta un programa con python -m pdb y practica los comandos n, s, c y p expresión.
  • Añade pruebas para una función de máximo y mínimo que definan el comportamiento esperado para una lista normal, una lista de un elemento y una lista vacía.

Bibliografía y fuentes

Presentación

Puedes repasar los conceptos principales con la presentación de depuración de la unidad 2.4.