Cómo utilizar pdb, el debugger de Python
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 invertirá gran parte de su tiempo en la resolución de bugs o defectos presentes 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)
Este 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.
Inicialmente, 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 las líneas adyacentes al punto 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 la pantalla. 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 únicamente ejecute la siguiente línea de
código. 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.