Mostrando entradas con la etiqueta depurador. Mostrar todas las entradas
Mostrando entradas con la etiqueta depurador. Mostrar todas las entradas

martes, 9 de julio de 2019

Facilitando la depuración de programas con breakpoint()




Python 3.7 incluye una mejora que facilita a los desarrolladores el acceso al depurador de programas de Python (Pdb). Antes para poder invocar al depurador era necesario incluir en un programa la siguiente línea:

import pdb; pdb.set_trace()

Esto ya no es necesario. Ahora para depurar el código simplemente tendremos que insertar la función breakpoint() en el lugar o lugares donde se quieran establecer los puntos de interrupción. Con este cambio se persigue facilitar la tarea, hacerla más cómoda e intuitiva.

Después, una vez iniciado el proceso de depuración todo funciona como hasta ahora: cuando se alcanza un punto de interrupción se hace una parada temporal y se devuelve el control al usuario para ejecutar el programa paso a paso (n) o hasta el siguiente punto de interrupción (c), consultar el valor de las variables, etc. Veamos un caso práctico.

El siguiente ejemplo contiene una lista de valores a sumar. Para ello, se recorren, uno a uno, los distintos elementos y se van sumando a la variable acumula, que inicialmente tiene el valor 0. El programa sumando.py genera una excepción cuando se alcanza el cuarto elemento ('a') por ser de tipo cadena; y por no poder sumarse a un valor numérico. En este caso para la depuración se incluye el punto de interrupción justo antes de la suma del elemento en curso para permitir evaluar su valor.

sumando.py:

lista = [2, 1, 3, 'a', 4]
acumula = 0
for valor in lista:
    breakpoint()
    acumula+=valor
    print(acumula)

Para iniciar la depuración, ejecutar:

$ python3 sumando.py

Una vez iniciada, el programa avanzará hasta el punto de interrupción. Después, si escribimos el nombre de alguna de las variables (valor o acumula) y presionamos [return] obtendremos su valor actual. A continuación, al pulsar 'c' y [return] el programa mostrará el resultado de la primera suma y completará un ciclo hasta alcanzar de nuevo el punto de interrupción. Si repetimos la acción y vamos consultando la variable valor observaremos que el error se produce cuando se alcanza el cuarto elemento de la lista:

TypeError: unsupported operand type(s) for +=: 'int' and 'str'

Para obtener ayuda de otras opciones del depurador escribir 'h' y [return]. Para ampliar la información de ayuda de alguna opción escribir: 'h' [opción] y [return]. Y para salir del depurador escribir 'exit', 'quit' o 'q' y [return].

Para ejecutar el programa omitiendo la depuración:

$ PYTHONBREAKPOINT=0 python3 sumando.py

Y para ejecutar el programa con el depurador de consola PuDB:

$ PYTHONBREAKPOINT=pudb.set_trace python3 sumando.py


Relacionado:


Ir al índice del tutorial de Python

sábado, 6 de octubre de 2018

El depurador PuDB




PuDB es un depurador ligero de código para Python con una interfaz de texto que proporciona todo lo necesario para probar programas y encontrar los errores de lógica que puedan producirse mientras se ejecutan.

Durante la ejecución del código se aplica color a su sintaxis para facilitar su lectura y se muestra información en tiempo real de las variables (y de otros objetos), de la pila de ejecución y de los puntos de parada que se establezcan para el análisis. Todo ello, distribuido en áreas separadas que favorecen su seguimiento. También, existe un área a la que es posible acceder en cualquier momento para ejecutar comandos Python o acceder a uno de los Shell disponibles (ipython, ptpython, bpython, etc).

La navegación entre las distintas áreas y el modo de interactuar con el depurador se hace mediante la pulsación de teclas lográndose que la tarea de depuración sea fácil y rápida.


Instalación de PuDB


Instalación con pip:

$ pip3 install pudb


Uso básico de PuDB


Para explicar el funcionamiento del depurador utilizaremos el código del siguiente ejemplo que incluye una función funcion1, a todas luces innecesaria, y varias variables que modifican su valor a medida que se ejecutan dos bucles while. Se trata de ejecutar el programa y hacer un seguimiento de sus distintos objetos para localizar un error y subsanarlo. El programa que cuenta con dos contadores (var1 y var2) debe presentar en pantalla sus valores mostrando como var1 se va incrementando de 1 en 1 (desde 1 a 10) cada vez que var2 completa un ciclo que va desde 10 a 1 (decreciendo de 1 en 1). También hay una variable de tipo lista lista1 a la que se va añadiendo el valor que va tomando var1 en cada ciclo, que se muestra al finalizar.

bucles.py:

def funcion1(par1):
    return par1 + 1
    
var1 = 0
lista1 = []

while var1 < 10:
    var2 = 10    
    var1 = funcion1(var1)
    lista1.append(var1)    
    while var2 > 0:
        print(var1, var2)
        var2=-1

print(lista1)

Si ejecutamos el código con:

$ python3 bucles.py

Comprobaremos que la salida que se obtiene es errónea, que no se corresponde con lo esperado. En ella var1 se incrementa de 1 en 1 pero var2 se mantiene siempre con el valor 10. Según lo comentado con anterioridad antes de que var1 cambie al siguiente valor, var2 tiene que ir desde 10 a 1, decreciendo de 1 en 1.

Salida errónea:

1 10
2 10
3 10
4 10
5 10
6 10
7 10
8 10
9 10
10 10
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Para comenzar la depuración del programa insertar al principio del código la siguiente línea:

from pudb import set_trace

Después, iniciar el depurador con:

$ pudb3 prueba.py

La primera vez que se inicia el depurador se muestra la ventana de bienvenida con información general de la versión. Para avanzar presionar [return].




Después hay que configurar las preferencias del depurador en la ventana Edit Preferences: básicamente, entre sus opciones hay que seleccionar si se van a mostrar o no los números de líneas junto al código, si habrá que confirmar o no la salida del depurador, el Shell Python que se utilizará de forma predeterminada, el tema o combinación de colores de la interfaz, el orden de la pila de ejecución y el modo de mostrar las variables.

Para desplazarse entre las opciones utilizar las fechas de desplazamiento del cursor [arriba] y [abajo] y para seleccionar o deseleccionar una opción la [barra espaciadora]. Cuando se hayan establecido todas las opciones, para aceptarlas, presionar la flecha [derecha] para situar el cursor en la ventana de las opciones OK y Cancel. Seleccionar OK y confirmar con [return].

Más adelante, cuando se desee acceder a la ventana Edit Preferences para cambiar alguna opción, presionar [Control+p]. Toda la información de la configuración se guarda en el archivo ~/.config/pudb/pudb.cfg.

A continuación, se mostrará en pantalla la interfaz de PuDB con el código del programa a depurar en el área mayor; a su derecha se muestran las áreas para seguimiento de Variables, Stack y Breakpoints; y en la parte inferior el área Command line para ejecutar comandos Python.

Antes de iniciar la depuración podemos insertar uno o más puntos de parada en las líneas de código que consideremos más adecuadas para la exploración. Para ello, utilizar las teclas de flecha [arriba] y [abajo] para desplazar el selector de línea y presionar [b] para insertar o eliminar un punto de parada.




En este caso desplazar el selector de línea a la línea 14 ("print(var1, var2)") utilizando la tecla de flecha [abajo] y presionar la tecla [b] para insertar un punto de parada (la línea aparecerá con el color rojo de fondo). Después, para iniciar la ejecución desde la primera línea hasta alcanzar dicho punto presionar [c].

En este momento el programa se encuentra detenido en la línea 14. En la ventana Variables aparecen ya los valores actuales de las variables e información de otros objetos: var1 = 1, var2 = 10, etc. A partir de ahora podemos avanzar línea a línea presionando la tecla [n]. A medida que pulsamos y completamos ciclos observamos en el área Variables que el valor de var2 cambia su valor de 10 a -1, alternando estos valores en vez de ir decreciendo de 1 en 1 como estaba previsto. Concretamente, al pasar por la línea 15 se asigna erróneamente el valor -1, cuando en realidad habría que restar 1 a var2. Para subsanarlo salir del depurador presionando la tecla [q] y cambiar el código de la línea 15 que es 'var2=-1' por 'var2-=1'. Después, probar la ejecución y el resultado debe ser correcto. Si suprimimos el punto de parada actual presionando la tecla [b] en la línea 14 y después presionamos la tecla [c] el programa se ejecutará hasta el final.

Una ventaja de PuDB es que "recuerda" los puntos de parada para futuras sesiones porque se guardan en el archivo ~/.config/pudb/saved-breakpoints. Estos puntos de parada también pueden establecerse insertando en el código la función set_trace().

Durante la depuración para cambiar al área Variables presionar [V]. En este área se puede modificar el modo de visualizar un objeto. Por ejemplo, en vez de visualizar el número de elementos que contiene una lista se pueden ver los propios elementos.

Para cambiar entre las áreas se pueden usar las teclas de desplazamiento del cursor en cualquier dirección. Por ejemplo, al presionar la tecla de flecha [abajo] cada vez que se alcance el límite inferior de un área se pasará al área inmediata de abajo. Esto funciona en cualquier sentido según se presione flecha [arriba], [abajo], [izquierda] o [derecha]. Otra alternativa para cambiar de área directamente es mediante las teclas siguientes:

  • [C] - Code 
  • [V] - Variables 
  • [S] - Stack 
  • [B] - Breakpoints

También, desde cualquier área se puede pasar al área Command Line para ejecutar comandos Python presionando [Control-x] o pasar de ésta al área del código con la misma combinación de teclas.

Por último, si se desea iniciar desde PuDB el Shell configurado en las opciones de preferencia presionar [!]. Y para ver información de ayuda de otros usos del teclado pulsar [?].

Hay distintas alternativas para depurar programas Python pero para los amantes del Terminal y, en particular, de las interfaces de texto en PuDB encontrarán un buen aliado.

Relacionado:


Ir al índice del tutorial de Python


viernes, 6 de febrero de 2015

Solucionando errores con el depurador




El módulo de depuración pdb


El módulo pdb de la librería estándar es una ayuda importante para los programadores Python. Se utiliza para analizar si un programa hace lo que se espera de él. En esencia facilita la detección de errores de codificación y agiliza su corrección.

Antes de continuar, es necesario entender que un depurador no se ocupa de los errores de sintaxis pues esta tarea la tiene encomendada el propio intérprete; y a dicha función se entrega incondicionalmente mientras se ejecuta un programa (.py), o bien, durante el proceso de compilación cuando se desea obtener el correspondiente archivo compilado o bytecode (.pyc).

Fundamentalmente, el módulo pdb permite ejecutar un programa, instrucción a instrucción; detenerlo en un punto específico (breakpoint) para poder examinar el valor de alguna variable o cambiar su valor antes de continuar; y/o ejecutar alguna instrucción para verificar algún resultado. Todo ello lo hace teniendo el control del programa en todo momento, hasta que termine su ejecución de la forma prevista o hasta que su finalización sea forzada.

Tipos de errores


Durante el desarrollo de un programa podemos encontrarnos, básicamente, con tres tipos de errores:

  • Los errores de interpretación o compilación que son producidos por instrucciones mal escritas o por un uso incorrecto de la sintaxis del lenguaje. En Python, estos errores tienen que ser solucionados para que un programa pueda ser ejecutado.
  • Los errores de ejecución que se producen mientras se ejecuta un programa, a pesar de estar todo el código escrito conforme a las reglas del lenguaje. Pueden suceder por inconsistencia de los datos, al intentar dividir por 0, por no poder acceder a un dispositivo, por otros errores externos al programa, etc.
  • Y los errores de lógica que son aquellos derivados de una errónea codificación y que implican que el programa no alcance el propósito para el que fue concebido.

Uso del depurador


Para diferenciar algunos errores de los comentados y ver cómo funciona el depurador lo mejor será basarnos en un caso práctico.

A continuación, vamos a escribir un programa muy simple llamado (cambia.py) que cuando se ejecute pedirá que se introduzca una cadena de caracteres. La introducción terminará al presionar la tecla [return]. Después, el programa buscará en la cadena introducida todos los caracteres que sean asteriscos "*" y los sustituirá por comas ",". Este programa incorpora dos errores diferentes que vamos a estudiar:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

entrada = input("Escribe algo: ")
entrada.replace("*", ",")
print("Salida: ", entrada)) 

Probemos a ejecutar el código desde la línea de comandos:

$ python3 cambia.py


El avezado intérprete nos avisa que tenemos un error en la línea del print(), en la número 6, donde aparece un paréntesis de cierre de más. Y por este motivo el programa no se puede ejecutar:


  File "cambia.py", line 6
    print("Salida: ", entrada))
                              ^
SyntaxError: invalid syntax


Este error de sintaxis podemos reproducirlo si intentamos compilar el código con:

$ python3 -m py_compile cambia.py


Desde la versión 3.2 de Python cuando se compila un programa, si no hay errores, se crea al mismo nivel del archivo del programa una carpeta llamada __pycache__ y, en ella, se genera su versión compilada o bytecode. 

En el nombre del archivo compilado aparece la versión del intérprete con que se compiló y su extensión, que como ya hemos comentado, será .pyc. En este caso el nombre será "cambia.cpython-34.pyc" y contendrá el literal "cpython". 

CPython es el nombre que se da a la implementación oficial y original de Python, (la que instalamos desde Python.org), escrita en lenguaje C, que es también el propio intérprete del bytecode. Como seguramente conoce el lector hay otras implementaciones de Python como: Jython, PyPy, IronPython.

La razón de que aparezca la versión del intérprete en el nombre del archivo compilado está en la posibilidad de tener instaladas varias versiones diferentes de Python en un mismo sistema y en que cada bytecode está ligado a la versión del intérprete que lo generó.

El archivo compilado o bytecode no podemos editarlo pero sí ejecutarlo desde la línea de comandos:

$ python3 cambia.cpython-34.pyc

Después de comentar algo sobre la compilación, lo siguiente será editar el programa fuente y arreglar el error quitando el paréntesis de más. Después, volveremos a ejecutar con:

$ python3 cambia.py

Y como ya no hay ningún error de sintaxis el programa comenzará su ejecución y pedirá que escribamos algún texto. A continuación, escribiremos: "hola* buenos días" (sin las comillas) y terminaremos con la tecla [return]. Finalmente, el programa mostrará una salida incorrecta (el asterisco se tenía que haber sustituido por una coma y eso no ha ocurrido) y terminará su ejecución. Así que el programa no hace lo que debe (error de lógica) y, sin embargo, en este caso "nadie" nos avisa del fallo:

Escribe algo: hola* buenos días
Salida:  hola* buenos días

En este tipo de situaciones es donde conviene utilizar un depurador de código. Tengamos en cuenta que este programa es muy corto pero en uno con varios cientos de líneas podemos encontrarnos con dificultades.


Buscando el error

A continuación, volveremos a editar el programa y añadiremos al principio la siguiente línea que importa el módulo de depuración: 

import pdb 

Y después del input() insertamos otra línea que servirá para que el programa se detenga en ese lugar cuando sea ejecutado:

pdb.Pdb().set_trace()

Después de los últimos cambios el código quedará así:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pdb

entrada = input("Escribe algo: ")
pdb.Pdb().set_trace()
entrada.replace("*", ",")
print("Salida: ", entrada)


Si ejecutamos e introducimos "Hola* buenos días" y [return] el depurador detendrá el programa y mostrará la línea siguiente que se va a ejecutar:

Escribe algo: Hola* buenos días
> /home/antonio/Local/Blog/blog-python3/Nuevo/depurar.py(8)<module>()
-> entrada.replace("*", ",")
(Pdb)

Ahora, si escribimos a continuación de (Pdb) el nombre de la variable entrada el depurador retornará su valor actual: "Hola* buenos días":

(Pdb) entrada
'Hola* buenos días'
(Pdb)

En este punto si presionamos la tecla "n" (next) y [return] ejecutaremos la instrucción señalada con la flecha y el depurador se detendrá de nuevo en la línea siguiente:

(Pdb) n
> /home/antonio/Local/Blog/blog-python3/Nuevo/depurar.py(9)<module>()
-> print("Salida: ", entrada)
(Pdb)

Si volvemos a preguntar por el valor de la variable entrada comprobaremos que justo antes de la línea del print() no ha cambiado ¿Por qué?:

(Pdb) entrada
'Hola* buenos días'

En este punto podemos deducir que el error está en la línea donde se utiliza el método replace(), que aunque realiza los cambios, éstos no son reasignados a la variable entrada y se pierden. Lo correcto hubiera sido codificarlo así:

entrada = entrada.replace("*", ",")

Si arreglamos el error y ejecutamos de nuevo comprobaremos que ya funciona correctamente.

Escribe algo: Hola* buenos días
Salida:  Hola, buenos días

Otra prueba de depuración que podemos realizar consiste en modificar el valor de la variable entrada cuando el programa esté detenido y, después, con el comando "c" (continue) seguiremos su ejecución para ver qué sucede:

(Pbd) entrada = "Buenas tardes* América"


Otros comandos que se pueden utilizar con el depurador:

quit (q)
Finaliza la depuración.

list (l)
Lista líneas de código indicando el punto de parada.

step (s)
Ejecuta el programa paso a paso.

continue (c)
Continua ejecución hasta el final o hasta el siguiente punto de interrupción.

return (r)
Cuando se ejecuta una función continua la ejecución hasta return.

nombrevariable o display var1, var2
Muestra el valor de las variables indicadas.

var2 = var1 + int(valor)
Declarar variables auxiliares a partir de otras existentes.

help
Lista todos los comandos disponibles del depurador.

Para agregar depuración desde la línea de comandos sin necesidad de importar el módulo pdb:

$ python3 -m pdb cambia.py

Para concluir, comentar que el entorno de desarrollo IDLE cuenta con un depurador integrado con posibilidad de ejecutar instrucción a instrucción, insertar puntos de interrupción y permite ver la pila de llamadas.

Relacionado:


Ir al índice del tutorial de Python