Cómo utilizar pdb, el debugger de Python

BUG por Javier Pais. CC BY. Imagen con calidad y tamaño reducidos.

Los diseñadores de Python crearon pdb, una librería nativa cuyo único propósito es permitir a los desarrolladores inspeccionar la ejecución de sus programas de una forma fácil y rápida.

Es una realidad que un programador o desarrollador de software invertirá gran parte de su tiempo en la resolución de bugs o defectos en aplicaciones de software, ya sea en la fase de desarrollo o de producción.

Con pdb es posible insertar breakpoints en el código fuente sin necesidad de usar un IDE o herramientas externas. Cómo cualquier debugger, pdb permite imprimir el valor de las variables, evaluar el código fuente línea por línea e inspeccionar el funcionamiento interno de las funciones o métodos en un script Python.

En éste artículo explicaremos como utilizar las características más comunes que ofrece esta poderosa herramienta.

Script de prueba

Para comenzar necesitamos escribir un script en Python bastante sencillo que utilizaremos para explicar el funcionamiento de pdb. Lo vamos a guardar con el nombre pdb-script.py:

#!/usr/bin/env python

def sum(list):
    sum = 0
    for item in list:
        sum += item
    return sum

if __name__ == '__main__':
    digits = []
    for i in range(10):
        digits.append(i)
    sum(digits)

El script genera una lista de números del cero al nueve y luego realiza una sumatoria de todos los números en la lista. Al ejecutar el script mediante el comando:

$ python pdb-script.py

Tristemente notaremos que no se imprime nada en la pantalla.

Importando pdb

Para saber que está sucediendo, vamos a utilizar pdb para inspeccionar cada una de las líneas de código en el script. En primer lugar debemos importar la librería. Agregamos el siguiente fragmento de código justo después de la línea shebang #!/usr/bin/env python:

import pdb; pdb.set_trace()

Y ejecutamos el script de nuevo. Veremos algo como esto en la terminal:

> /ubicacion/pdb-script.py(4)<module>()
-> def sum(list):
(Pdb)

Esto significa que la ejecución del programa se detuvo en la primera línea del script después del punto donde se importó la librería.

En primer lugar, pdb nos indica la ubicación del script, y entre paréntesis, el número de la línea del script en donde se encuentra detenido el programa.

Enseguida, pdb muestra el código fuente correspondiente al punto donde se encuentra detenido el programa.

Para finalizar, pdb muestra un prompt. Aquí podemos ingresar comandos para indicarle a pdb lo que debe hacer a continuación.

Creando breakpoints

Los breakpoints se utilizan para indicarle a pdb en donde deseamos que la ejecución del programa se detenga para inspeccionar detalladamente un determinado fragmento de código.

Para insertar un breakpoint, utilizamos el comando break de pdb. Este comando recibe como argumento la línea en la cual deseamos insertar el breakpoint. Para usar el comando, simplemente escribimos break en el prompt de pdb de la siguiente forma:

(Pdb) break 11

La respuesta de pdb es la siguiente:

Breakpoint 1 at /ubicacion/pdb-script.py:11
(Pdb)

Lo que nos indica que el breakpoint número uno fue creado en la línea número once del script.

Reanudando la ejecución del programa

Si en algún momento deseamos que la ejecución del programa continue normalmente, podemos usar el comando continue de pdb:

(Pdb) continue

El programa se detendrá al encontrar un nuevo breakpoint o terminará de ejecutar el script por completo si no encuentra uno. Si ejecutamos este comando ahora, el programa se detendrá en la línea número once debido al breakpoint que definimos anteriormente:

> /ubicacion/pdb-script.py(11)<module>()
-> digits = []
(Pdb)

Mostrando código adyacente

En una sesión de debugging es muy fácil perder el rastro al fragmento de código que estamos inspeccionando. Sería muy útil poder ver la línea donde esta detenido el programa en un momento determinado.

Afortunadamente pdb nos proporciona el comando list. Este comando permite mostrar las líneas de código adyacentes al punto donde está detenida la ejecución del programa:

(Pdb) list
 6         for item in list:
 7             sum += item
 8         return sum
 9
10     if __name__ == '__main__':
11 B->     digits = []
12         for i in range(10):
13             digits.append(i)
14         sum(digits)
[EOF]
(Pdb)

Podemos ver con facilidad que el programa esta detenido en la línea número once, gracias al símbolo B-> que aparece en el texto. Por defecto, pdb imprime once líneas alrededor del punto donde está detenido el programa. El símbolo B nos indica que en esa línea hay un breakpoint definido.

Avanzando línea por línea

Ahora vamos a indicarle a pdb que ejecute la siguiente línea de código solamente. Para esto utilizamos el comando next:

(Pdb) next
> /ubicacion/pdb-script.py(12)<module>()
-> for i in range(10):
(Pdb)

pdb nos indica que la línea once ya se ejecutó y ahora el programa está detenido en la línea número doce, esperando instrucciones.

Inspeccionando variables

En este punto, la variable digits ya fue procesada. Para inspeccionar el valor de la variable digits, utilizamos el comando p de pdb seguido del nombre de la variable:

(Pdb) p digits
[]
(Pdb)

Podemos ver que, por el momento, la variable digits es un array o lista sin elementos, indicado por el símbolo [].

Ahora queremos comprobar que el bucle for esta construyendo la lista de forma apropiada. Vamos a ejecutar el comando next dos veces seguidas para asegurarnos que se ejecute el primer ciclo en el bucle for. Debemos ver algo como esto en la terminal:

(Pdb) next
> /ubicacion/pdb-script.py(13)<module>()
-> digits.append(i)
(Pdb) next
> /ubicacion/pdb-script.py(12)<module>()
-> for i in range(10):
(Pdb)

La lista ahora debe contener un elemento: el número cero. Vamos a comprobar si esto es cierto o no, inspeccionando de nuevo la variable digits mediante el comando p:

(Pdb) p digits
[0]
(Pdb)

¡Hurra! La lista de números parece estar construyéndose correctamente. Como ejercicio, el lector puede comprobar los siguientes ciclos del bucle y ver como la lista va creciendo hasta completar diez números.

Inspeccionando el código dentro de una función

Ahora que ya sabemos que la lista de números construida por el script es correcta, vamos a comprobar la confiabilidad de la función sum(). En primer lugar vamos a crear un nuevo breakpoint en la línea donde invocamos a la función sum(), es decir la número catorce:

(Pdb) break 14
Breakpoint 2 at /ubicacion/pdb-script.py:14
(Pdb)

Luego, ejecutamos el comando continue de pdb para que el programa se ejecute normalmente hasta el breakpoint que acabamos de definir:

(Pdb) continue
> /ubicacion/pdb-script.py(14)<module>()
-> sum(digits)
(Pdb)

Para que pdb pueda inspeccionar el código dentro de la función sum(), utilizamos el comando step:

(Pdb) step
--Call--
> /ubicacion/pdb-script.py(3)sum()
-> def sum(list):
(Pdb)

Podemos observar que la ejecución del programa se traslada a la primera línea de la definición de la función sum() y se detiene allí. ¿Que pasará si ejecutamos el comando next? Dejamos como ejercicio al lector la inspección del código completo de la función usando lo aprendido hasta el momento.

Al finalizar la inspección de la función, pdb retorna la ejecución del programa a la secuencia principal:

(Pdb) next
--Return--
> /ubicacion/pdb-script.py(14)<module>()->None
-> sum(digits)
(Pdb)

Borrando breakpoints

Para finalizar, vamos a borrar todos los breakpoints que hemos creado, utilizando para ello el comando clear de pdb:

(Pdb) clear
Clear all breaks? y
(Pdb)

pdb nos preguntará si queremos borrar todos los breakpoints, a lo que respondemos afirmativamente. También podemos borrar breakpoints específicos pasando como argumento al comando clear, el número de cada breakpoint:

(Pdb) clear 1
Deleted breakpoint 1
(Pdb)

En este caso estaremos borrando el breakpoint número uno solamente.