Depuración de aplicaciones Python con el módulo PDB

En este tutorial, vamos a aprender cómo usar el módulo PDB de Python para depurar aplicaciones de Python. La depuración se refiere al proceso de eliminación de software...

Introducción

En este tutorial, vamos a aprender a usar el módulo AP de Python para depurar aplicaciones de Python. La depuración se refiere al proceso de eliminar errores de software y hardware de una aplicación de software. PDB significa "Python Debugger", y es un depurador de código fuente interactivo integrado con una amplia gama de funciones, como pausar un programa, ver valores de variables en instancias específicas, cambiar esos valores, etc.

En este artículo, cubriremos las funcionalidades más utilizadas del módulo PDB.

Fondo

La depuración es una de las actividades más desagradables en el desarrollo de software y, al mismo tiempo, es una de las tareas más importantes en el ciclo de vida del desarrollo de software. En algún momento, cada programador tiene que depurar su código, a menos que esté desarrollando una aplicación de software muy básica.

Hay muchas maneras diferentes de depurar una aplicación de software. Un método muy común es usar las declaraciones "imprimir" en diferentes instancias de su código para ver qué sucede durante la ejecución. Sin embargo, este método tiene muchos problemas, como la adición de código adicional que se usa para imprimir los valores de las variables, etc. Si bien este enfoque podría funcionar para un programa pequeño, el seguimiento de estos cambios de código en una aplicación grande con muchas líneas de El código, distribuido en diferentes archivos, puede convertirse en un gran problema. El depurador nos resuelve ese problema. Nos ayuda a encontrar las fuentes de error en una aplicación usando comandos externos, por lo tanto, no hay cambios en el código.

Nota: Como se mencionó anteriormente, PDB es un módulo integrado de Python, por lo que no es necesario instalarlo desde una fuente externa.

Comandos de teclado

Para comprender los principales comandos o herramientas que tenemos a nuestra disposición en PDB, consideremos un programa básico de Python y luego intentemos depurarlo usando los comandos de PDB. De esta forma, veremos con un ejemplo qué hace exactamente cada comando.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Filename: calc.py

operators = ['+', '-', '*', '/']
numbers = [10, 20]

def calculator():
    print("Operators available: ")
    for op in operators:
        print(op)

    print("Numbers to be used: ")
    for num in numbers:
        print(num)

def main():
    calculator()

main()

Aquí está el resultado del script anterior:

1
2
3
4
5
6
7
8
Operators available:
+
-
*
/
Numbers to be used:
10
20

No he agregado ningún comentario en el código anterior, ya que es fácil de usar para principiantes y no involucra conceptos ni sintaxis complejos. No es importante tratar de entender la "tarea" que este código logra, ya que su propósito era incluir ciertas cosas para que todos los comandos de PDB pudieran probarse en él. Muy bien, ¡comencemos!

El uso de PDB requiere el uso de la interfaz de línea de comandos (CLI), por lo que debe ejecutar su aplicación desde la terminal o el símbolo del sistema.

Ejecute el siguiente comando en su CLI:

1
$ python -m pdb calc.py

En el comando anterior, el nombre de mi archivo es "calc.py", por lo que deberá insertar su propio nombre de archivo aquí.

Nota: -m es un indicador y notifica al ejecutable de Python que se debe importar un módulo; esta bandera es seguida por el nombre del módulo, que en nuestro caso es pdb.

La salida del comando se ve así:

1
2
3
> /Users/junaid/Desktop/calc.py(3)<module>()
-> operators = [ '+', '-', '*', '/' ]
(Pdb)

La salida siempre tendrá la misma estructura. Comenzará con la ruta del directorio a nuestro archivo de código fuente. Luego, entre paréntesis, indicará el número de línea de ese archivo al que PDB apunta actualmente, que en nuestro caso es "(3)". La siguiente línea, que comienza con el símbolo "->", indica la línea a la que se apunta actualmente.

Para cerrar el indicador de PDB, simplemente ingrese quit o exit en el indicador de PDB.

Algunas otras cosas a tener en cuenta, si su programa acepta parámetros como entradas, también puede pasarlos a través de la línea de comando. Por ejemplo, si nuestro programa anterior hubiera requerido tres entradas del usuario, entonces nuestro comando se vería así:

1
$ python -m pdb calc.py var1 var2 var3

Continuando, si anteriormente cerró el indicador de PDB a través del comando quit o exit, vuelva a ejecutar el archivo de código a través de PDB. Después de eso, ejecute el siguiente comando en el indicador de PDB:

1
(Pdb) list

La salida se ve así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  1     # Filename: calc.py
  2
  3  -> operators = ['+', '-', '*', '/']
  4     numbers = [10, 20]
  5
  6     def calculator():
  7         print("Operators available: ")
  8         for op in operators:
  9             print(op)
 10
 11         print("Numbers to be used: ")
(Pdb)

Esto le mostrará las primeras 11 líneas de su programa, con "->" apuntando hacia la línea actual que está ejecutando el depurador. A continuación, pruebe este comando en el indicador de PDB:

1
(Pdb) list 4,6

Este comando debería mostrar solo las líneas seleccionadas, que en este caso son las líneas 4 a 6. Aquí está el resultado:

1
2
3
4
  4     numbers = [10, 20]
  5
  6     def calculator():
(Pdb)

Depuración con puntos de interrupción

La siguiente cosa importante que aprenderemos es el punto de interrupción. Los puntos de interrupción generalmente se usan para programas más grandes, pero para comprenderlos mejor, veremos cómo funcionan en nuestro ejemplo básico. Los puntos de ruptura son ubicaciones específicas que declaramos en nuestro código. Nuestro código se ejecuta hasta esa ubicación y luego se detiene. A estos puntos se les asignan números automáticamente por PDB.

Tenemos las siguientes opciones diferentes para crear puntos de quiebre:

  1. Por número de línea
  2. Por declaración de función
  3. Por una condición

Para declarar un punto de interrupción por número de línea, ejecute el siguiente comando en el indicador de PDB:

1
(Pdb) break calc.py:8

Este comando inserta un punto de interrupción en la octava línea de código, que pausará el programa una vez que llegue a ese punto. La salida de este comando se muestra como:

1
2
Breakpoint 1 at /Users/junaid/Desktop/calc.py: 8
(Pdb)

Para declarar puntos de interrupción en una función, ejecute el siguiente comando en el indicador de PDB:

1
(Pdb) break calc.calculator

Para insertar un punto de interrupción de esta manera, debe declararlo usando el nombre del archivo y luego el nombre de la función. Esto genera lo siguiente:

1
Breakpoint 2 at /Users/junaid/Desktop/calc.py:6

Como puede ver, a este punto de interrupción se le ha asignado el número 2 automáticamente, y también se muestra el número de línea, es decir, 6, en el que se declara la función.

Los puntos de ruptura también se pueden declarar mediante una condición. En ese caso, el programa se ejecutará hasta que la condición sea falsa y se detendrá cuando esa condición se vuelva verdadera. Ejecute el siguiente comando en el indicador de PDB:

1
(Pdb) break calc.py:8, op == "*"

Esto rastreará el valor de la variable op a lo largo de la ejecución y solo se interrumpirá cuando su valor sea "*" en la línea 8.

Para ver todos los puntos de interrupción que hemos declarado en forma de lista, ejecute el siguiente comando en el indicador de PDB:

1
(Pdb) break

La salida se ve así:

1
2
3
4
5
6
7
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /Users/junaid/Desktop/calc.py: 8
2   breakpoint   keep yes   at /Users/junaid/Desktop/calc.py: 6
    breakpoint already hit 1 time
3   breakpoint   keep yes   at /Users/junaid/Desktop/calc.py: 8
    stop only if op == "*"
(Pdb)

Por último, veamos cómo podemos deshabilitar, habilitar y borrar un punto de interrupción específico en cualquier instancia. Ejecute el siguiente comando en el indicador de PDB:

1
(Pdb) disable 2

Esto deshabilitará el punto de interrupción 2, pero no lo eliminará de nuestra instancia de depuración.

En la salida, verá el número del punto de interrupción deshabilitado.

1
2
Disabled breakpoint 2 at /Users/junaid/Desktop/calc.py:6
(Pdb)

Imprimamos la lista de todos los puntos de interrupción nuevamente para ver el valor "Enb" para el punto de interrupción 2:

1
(Pdb) break

Producción:

1
2
3
4
5
6
7
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /Users/junaid/Desktop/calc.py:8
2   breakpoint   keep no    at /Users/junaid/Desktop/calc.py:4 # you can see here that the "ENB" column for #2 shows "no"
    breakpoint already hit 1 time
3   breakpoint   keep yes   at /Users/junaid/Desktop/calc.py:8
    stop only if op == "*"
(Pdb)

Para volver a habilitar el punto de interrupción 2, ejecute el siguiente comando:

1
(Pdb) enable 2

Y de nuevo, aquí está la salida:

1
Enabled breakpoint 2 at /Users/junaid/Desktop/calc.py:6

Ahora, si imprime la lista de todos los puntos de interrupción nuevamente, el valor de la columna "Enb" para el punto de interrupción 2 debería mostrar "sí" nuevamente.

Vamos ahora a borrar el punto de interrupción 1, que lo eliminará todo junto.

1
(Pdb) clear 1

La salida es la siguiente:

1
2
Deleted breakpoint 1 at /Users/junaid/Desktop/calc.py:8
(Pdb)

Si volvemos a imprimir la lista de puntos de interrupción, ahora solo debería mostrar dos filas de puntos de interrupción. Veamos la salida del comando "romper":

1
2
3
4
5
Num Type         Disp Enb   Where
2   breakpoint   keep yes   at /Users/junaid/Desktop/calc.py:4
    breakpoint already hit 1 time
3   breakpoint   keep yes   at /Users/junaid/Desktop/calc.py:8
    stop only if op == "*"

Exactamente lo que esperábamos.

Antes de continuar con esta sección, quiero mostrarle todo lo que se muestra cuando ejecutamos el código hasta el punto de interrupción especificado. Para hacerlo, eliminemos todos los puntos de interrupción anteriores y declaremos otro punto de interrupción a través del indicador PDB:

1. Borrar todos los puntos de interrupción

1
(Pdb) clear

Después de eso, escribe "y" y presiona "Enter". Debería ver aparecer una salida como esta:

1
2
Deleted breakpoint 2 at /Users/junaid/Desktop/calc.py:6
Deleted breakpoint 3 at /Users/junaid/Desktop/calc.py:8

2. Declarar un nuevo punto de interrupción

Lo que deseamos lograr es que el código se ejecute hasta el punto en que el valor de la variable num sea mayor que 10. Básicamente, el programa debe hacer una pausa antes de que se imprima el número "20".

1
(Pdb) break calc.py:13, num > 10

3. Ejecute el código hasta este punto de interrupción

Para ejecutar el código, use el comando "continuar", que ejecutará el código hasta que llegue a un punto de interrupción o finalice:

1
(Pdb) continue

Debería ver el siguiente resultado:

1
2
3
4
5
6
7
8
9
Operators available:
+
-
*
/
Numbers to be used:
10
> /Users/junaid/Desktop/calc.py(13)calculator()
-> print(num)

Esto es exactamente lo que esperábamos, el programa se ejecuta hasta ese punto y luego se detiene, ahora depende de nosotros si deseamos cambiar algo, inspeccionar variables o si queremos ejecutar el script hasta que finalice. Para indicarle que se ejecute hasta el final, ejecute el comando "continuar" nuevamente. La salida debe ser la siguiente:

1
2
3
4
20
The program finished and will be restarted
> /Users/junaid/Desktop/calc.py(3)<module>()
-> operators = [ '+', '-', '*', '/' ]

En el resultado anterior, se puede ver que el programa continúa exactamente desde donde lo dejó, ejecuta la parte restante y luego se reinicia para permitirnos depurarlo más si lo deseamos. Pasemos ahora a la siguiente sección.

Nota importante: antes de seguir adelante, borre todos los puntos de interrupción ejecutando el comando "borrar", seguido de escribir "y" en el indicador de PDB.

Funciones siguiente y paso

Por último, pero no menos importante, estudiemos las funciones siguiente y paso; estos se usarán con mucha frecuencia cuando comience a depurar sus aplicaciones, así que aprendamos qué hacen y cómo se pueden implementar.

Las funciones paso y siguiente se utilizan para iterar a lo largo de nuestro código línea por línea; hay una diferencia muy pequeña entre los dos. Mientras itera, si la función step encuentra una llamada de función, se moverá a la primera línea de la definición de esa función y nos mostrará exactamente lo que está sucediendo dentro de la función; mientras que, si la función siguiente encuentra una llamada de función, ejecutará todas las líneas de esa función de una sola vez y se detendrá en la siguiente llamada de función.

¿Confundido? Veamos eso en un ejemplo.

Vuelva a ejecutar el programa a través del indicador PDB usando el siguiente comando:

1
$ python -m pdb calc.py

Ahora escriba continue en el indicador de PDB y siga haciéndolo hasta que el programa llegue al final. Voy a mostrar una sección de toda la secuencia de entrada y salida a continuación, que es suficiente para explicar el punto. La secuencia completa es bastante larga y solo lo confundiría más, por lo que se omitirá.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
> /Users/junaid/Desktop/calc.py(1)<module>()
-> operators = [ '+', '-', '*', '/' ]
(Pdb) step
> /Users/junaid/Desktop/calc.py(2)<module>()
-> numbers = [ 10, 20 ]
.
.
.
.
> /Users/junaid/Desktop/calc.py(6)calculator()
-> print("Operators available: " )
(Pdb) step
Operators available:
> /Users/junaid/Desktop/calc.py(8)calculator()
-> for op in operators:
(Pdb) step
> /Users/junaid/Desktop/calc.py(10)calculator()
-> print(op)
(Pdb) step
+
> /Users/junaid/Desktop/calc.py(8)calculator()
-> for op in operators:
(Pdb) step
> /Users/junaid/Desktop/calc.py(10)calculator()
-> print(op)

.
.
.
.

Ahora, vuelva a ejecutar todo el programa, pero esta vez, use el comando "siguiente" en lugar de "paso". También he mostrado el seguimiento de entrada y salida para eso.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
> /Users/junaid/Desktop/calc.py(3)<module>()
-> operators = ['+', '-', '*', '/']
(Pdb) next
> /Users/junaid/Desktop/calc.py(4)<module>()
-> numbers = [10, 20]
(Pdb) next
> /Users/junaid/Desktop/calc.py(6)<module>()
-> def calculator():
(Pdb) next
> /Users/junaid/Desktop/calc.py(15)<module>()
-> def main():
(Pdb) next
> /Users/junaid/Desktop/calc.py(18)<module>()
-> main()
(Pdb) next
Operators available:
+
-
*
/
Numbers to be used:
10
20
--Return--

Muy bien, ahora que tenemos seguimiento de salida para ambas funciones, veamos en qué se diferencian. Para la función paso, puede ver que cuando se llama a la función calculadora, se mueve dentro de esa función y la recorre en "pasos", mostrándonos exactamente lo que sucede en cada paso.

Sin embargo, si ve el seguimiento de salida para la función siguiente, cuando se llama a la función "principal", no nos muestra qué sucede dentro de esa función (es decir, una llamada posterior a la función de calculadora), y luego imprime directamente el resultado final en un solo paso.

Estos comandos son útiles si está iterando a través de un programa y desea recorrer ciertas funciones, pero no otras, en cuyo caso puede utilizar cada comando para sus propósitos.

Conclusión

En este tutorial, aprendimos sobre una técnica sofisticada para depurar aplicaciones de Python utilizando un módulo integrado llamado PDB. Nos sumergimos en los diferentes comandos de solución de problemas que PDB nos proporciona, incluidas las declaraciones siguiente y paso, puntos de interrupción, etc. También los aplicamos a un programa básico para verlos en acción.

Licensed under CC BY-NC-SA 4.0