Saltar al contenido

Cómo depurar un programa Python con pdb

Introducción

En este artículo vamos a explicar cómo depurar un programa Python usando el módulo pdb de la biblioteca estándar. Si no sabes qué es exactamente depurar un programa o para qué te puede servir, sigue leyendo.

Depurar consiste en seguir el flujo de un programa a medida que se ejecuta, de forma que podemos monitorizar qué es lo que está sucediendo en cada momento. Es un método muy efectivo para encontrar fallos, porque:

  • Permite detener momentáneamente la ejecución del programa usando puntos de ruptura (breakpoints).
  • Permite examinar en cada momento las variables que se están utilizando (no necesitas llenar tu código de print).
  • Permite cambiar el valor de una variable mientras está detenida la ejecución.

Si es la primera vez que oyes hablar de esto, en seguida descubrirás el mundo de posibilidades que ofrece la depuración.

Puedes leer online la documentación del módulo pdb.
En esta entrada se ha usado python 3.3.2.

Primeros pasos

Vamos a utilizar este programa extraído del libro «Dive into Python 3»:

SUFFIXES = {
    1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
    1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
}

def approximate_size(size, a_kilobyte_is_1024_bytes=True):
    '''Convert a file size to human-readable form.

    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000

    Returns: string
    '''
    if size < 0:
        raise ValueError('number must be non-negative')

    multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
    for suffix in SUFFIXES[multiple]:
        size /= multiple
        if size < multiple:
            return '{0:.1f} {1}'.format(size, suffix)

    raise ValueError('number too large')

if __name__ == '__main__':
    print(approximate_size(1000000000000, False))
    print(approximate_size(1000000000000))

Que simplemente produce la siguiente salida:

$ python example.py
1.0 TB 931.3 GiB

La forma más directa de iniciar el depurador es con la línea:

$ python -m pdb example.py
> /home/juanlu/Development/Python/test/pdb/example.py(1)()
-> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
(Pdb)

De acuerdo, ¿qué acaba de suceder? Nos encontramos con un intérprete interactivo esperando órdenes, y la línea que empieza por -> es la línea donde se encuentra detenida la ejecución ahora mismo.

Lo primero que observamos es que podemos ejecutar cualquier instrucción Python que queramos:

(Pdb) 2 * 8
16
(Pdb) "Hello".lower()
'hello'
(Pdb) import numpy as np; np.sqrt(2)
1.4142135623730951

Para mostrar una lista de comandos disponibles, escribimos help. También podemos mostrar la ayuda de cada comando individial:

(Pdb) help
Documented commands (type help ):
EOF cl disable interact next return u where
a clear display j p retval unalias
alias commands down jump pp run undisplay
args condition enable l print rv unt
b cont exit list q s until
break continue h ll quit source up
bt d help longlist r step w
c debug ignore n restart tbreak whatis
Miscellaneous help topics:
exec pdb
(Pdb) help list
l(ist) [first [,last] | .]
List source code for the current file. Without arguments,
list 11 lines around the current line or continue the previous
listing. With . as argument, list 11 lines around the current
line. With one argument, list 11 lines starting at that line.
With two arguments, list the given range; if the second
argument is less than the first, it is a count.
The current line in the current frame is indicated by "->".
If an exception is being debugged, the line where the
exception was originally raised or propagated is indicated by
">>", if it differs from the current line.
(Pdb)

Como se puede leer, podemos usar el comando list para examinar el código fuente del archivo que estamos ejecutando. La primera vez que lo ejecutemos sin argumentos mostrará las 11 primeras líneas y marcará con -> la línea actual, y si seguimos ejecutándolo proseguirá avanzando. Veamos:

(Pdb) list
1 -> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
2 1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB',
3 'YiB']}
4
5 def approximate_size(size, a_kilobyte_is_1024_bytes=True):
6 '''Convert a file size to human-readable form.
7
8 Keyword arguments:
9 size -- file size in bytes
10 a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
11 if False, use multiples of 1000
(Pdb) list
12
13 Returns: string
14
15 '''
16 if size < 0:
17 raise ValueError('number must be non-negative')
18
19 multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
20 for suffix in SUFFIXES[multiple]:
21 size /= multiple
22 if size < multiple:

Ahora, un par de trucos:

  • ¿Has visto que, al mostrar la lista de comandos, había muchos con una sola letra? Lo que sucede es que son atajos: por ejemplo, l es un atajo para list, así que no tienes que escribir el comando entero.
  • Para repetir el último comando introducido, simplemente presiona Enter. Si quieres repetir el comando list tres veces, introduce l una vez y Enter otras dos. Más fácil imposible 🙂

Por ejemplo, para mostrar dónde estamos detenidos podemos usar el comando where o simplemente w:

(Pdb) w
/usr/lib/python3.3/bdb.py(405)run()
-> exec(cmd, globals, locals)
(1)()
> /home/juanlu/Development/Python/test/pdb/example.py(1)()
-> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],

Si queremos cerrar el depurador usaríamos quit, y si queremos que el programa continue hasta el final, usaremos continue o c:

(Pdb) c
1.0 TB
931.3 GiB
The program finished and will be restarted
> /home/juanlu/Development/Python/test/pdb/example.py(1)()
-> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
(Pdb)

El programa termina, se reinicia y estamos en el mismo punto que antes. De momento no hemos hecho nada demasiado interesante: comencemos 😉

Flujo e inspección de variables

Ahora, ¿cómo conseguimos ir avanzando en el flujo de ejecución? Para ello tenemos dos comandos: step (s) y next (n). Ambos ejecutan una línea del programa y avanzan a la siguiente, con la diferencia de que step se introduce dentro de las funciones cuando se invoca alguna. Volviendo a nuestro ejemplo:

The program finished and will be restarted
> /home/juanlu/Development/Python/test/pdb/example.py(1)()
-> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
(Pdb) n
> /home/juanlu/Development/Python/test/pdb/example.py(2)<module>()
-> 1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB',
(Pdb) # Presionamos Enter, lo mismo que ejecutar next otra vez
> /home/juanlu/Development/Python/test/pdb/example.py(3)<module>()
(Pdb)
> /home/juanlu/Development/Python/test/pdb/example.py(5)<module>()
-> def approximate_size(size, a_kilobyte_is_1024_bytes=True):
(Pdb)
> /home/juanlu/Development/Python/test/pdb/example.py(27)<module>()
-> if name == 'main':
(Pdb)
> /home/juanlu/Development/Python/test/pdb/example.py(28)<module>()
-> print(approximate_size(1000000000000, False))
(Pdb) # next ejecuta la línea pero no se introduce en approximate_size
1.0 TB
> /home/juanlu/Development/Python/test/pdb/example.py(29)<module>()
-> print(approximate_size(1000000000000))
(Pdb)

Hemos ido ejecutando las líneas una a una, y al llegar a la línea print y usar next se ha ejecutado también y ha saltado a la siguiente. Si para la siguiente línea print usamos step, nos introduciremos en el cuerpo de la función approximate_size y seguiremos depurando desde ahí:

> /home/juanlu/Development/Python/test/pdb/example.py(29)<module>()
-> print(approximate_size(1000000000000))
(Pdb) s
--Call--
> /home/juanlu/Development/Python/test/pdb/example.py(5)approximate_size()
-> def approximate_size(size, a_kilobyte_is_1024_bytes=True):
(Pdb)
> /home/juanlu/Development/Python/test/pdb/example.py(16)approximate_size()
-> if size < 0:
(Pdb) l . # Para ver dónde estamos
11 if False, use multiples of 1000
12
13 Returns: string
14
15 '''
16 -> if size < 0:
17 raise ValueError('number must be non-negative')
18
19 multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
20 for suffix in SUFFIXES[multiple]:
21 size /= multiple
(Pdb)

Estamos ahora en esa línea y no sabemos lo que va a suceder. ¿Cómo averiguamos el valor de una variable? Usando el comando print (p):

(Pdb) p size
1000000000000
(Pdb) p a_kilobyte_is_1024_bytes
True
(Pdb)

Y si ahora nos apetece salir del cuerpo de la función y volver al programa principal, escribimos return (r):

(Pdb) r
--Return--
> /home/juanlu/Development/Python/test/pdb/example.py(23)approximate_size()->'931.3 GiB'
-> return '{0:.1f} {1}'.format(size, suffix)
(Pdb)
931.3 GiB
--Return--
> /home/juanlu/Development/Python/test/pdb/example.py(29)<module>()->None
-> print(approximate_size(1000000000000))
(Pdb)

Puntos de ruptura

Imagina que tienes un programa muy largo y no quieres ir línea por línea desde el principio hasta el punto que te interesa. Para eso existen los puntos de ruptura: si estableces un punto de ruptura en una línea, el comando continue ejecutará el programa sin depuración hasta que encuentre uno, y entonces se detendrá. Si vuelves a usar continue el depurador se volverá a detener en el siguiente punto de ruptura, y así sucesivamente hasta que ya no queden y el programa finalice, como vimos al principio del artículo.

Para establecer un punto de ruptura se utiliza el comando break (b):

$ python -m pdb example.py
> /home/juanlu/Development/Python/test/pdb/example.py(1)<module>()
-> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
(Pdb) l
1 -> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
2 1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB',
3 'YiB']}
4
5 def approximate_size(size, a_kilobyte_is_1024_bytes=True):
6 '''Convert a file size to human-readable form.
7
8 Keyword arguments:
9 size -- file size in bytes
10 a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
11 if False, use multiples of 1000
(Pdb)
12
13 Returns: string
14
15 '''
16 if size < 0:
17 raise ValueError('number must be non-negative')
18
19 multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
20 for suffix in SUFFIXES[multiple]:
21 size /= multiple
22 if size < multiple:
(Pdb) b 21
Breakpoint 1 at /home/juanlu/Development/Python/test/pdb/example.py:21
(Pdb) c
> /home/juanlu/Development/Python/test/pdb/example.py(21)approximate_size()
-> size /= multiple
(Pdb)

Hemos establecido un punto de ruptura en la línea 21 del programa, y a continuación el depurador lo ha ejecutado hasta llegar a dicha línea. Y a partir de ahí todo funciona igual que antes 🙂

Otra forma de establecer un punto de ruptura en tu programa es incluir la siguiente línea:

multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
for suffix in SUFFIXES[multiple]:
import pdb; pdb.set_trace()
size /= multiple
if size < multiple:

De este modo, al ejecutarlo saltará el depurador directamente:

$ python example.py
> /home/juanlu/Development/Python/test/pdb/example.py(22)approximate_size()
-> size /= multiple
(Pdb)

¿Te ha ayudado esto a encontrar ese error que se te resistía? Cuéntanos en los comentarios 🙂

¡Un saludo!

10 comentarios en «Cómo depurar un programa Python con pdb»

  1. Muy buen articulo! muy completo .. muchos IDE traen un alias para cuando escribes “debug” te agregue la linea de pdb… en vim (mi editor) yo la bindee asi en el .vimrc:
    au FileType python iab debug import pdb; pdb.set_trace()

  2. Hermano buen aporte, siempre me había negado a usar el depurador de python por lo complejo que se veía y me conformaba con los print, pero se hizo muy tedioso poner y quitar print aqui y allá ademas que consume más tiempo, con esta intro que has dado, me doy cuenta que es muy fácil depurar con pdb, gracias, saludo desde Venezuela.

    1. Muchas gracias por tu comentario Antony 🙂 Lo bueno de aprender a usar pdb es que los mismos conceptos te valen para depurar casi todos los lenguajes que existen. Por cierto, ¿sabes que está resurgiendo la comunidad Python en Venezuela? http://www.python2.org.ve/ ¡Saludos desde España!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

− 1 = two

Pybonacci