pdb - Debugger Interattivo

Scopo Il debugger interattivo di Python
Versione Python 1.4 e superiore

Il modulo pdb implementa un ambiente di debug interattivo per i programmi Python. Tra le caratteristiche include la possibilità di mettere in pausa il proprio programma, guardare il valore delle variabili, ed osservare l'esecuzione del programma passo passo, in modo da comprendere cosa sta attualmente facendo il proprio programma per trovare bachi nella logica.

Lanciare il Debugger

Il primo passo per usare pdb è fare sì che l'interprete entri nel debugger quando lo si vuole. Ci sono alcuni modi diversi per farlo, a seconda delle condizioni di partenza e di quello che si deve sottoporre a debug.

Da Riga di Comando

Il modo più semplice per usare il debugger è quello di farlo partire dalla riga di comando, passando il nome del proprio programma come input, in modo che sappia cosa eseguire.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

class MyObj(object):

    def __init__(self, num_loops):
        self.count = num_loops

    def go(self):
        for i in range(self.count):
            print i
        return

if __name__ == '__main__':
    MyObj(5).go()

L'esecuzione del debugger da riga di comando fa sì che esso carichi il proprio file sorgente e che l'esecuzione venga arrestata alla prima istruzione trovata. In questo casi si interrompe prima di valutare la definizione della classe MyObj alla riga n. 7.

$ python -m pdb pdb_script.py
> .../pdb_script.py(7)()
-> """
(Pdb)
In genere pdb include il percorso completo di ogni modulo nell'output quando si stampa il nome di un file. Allo scopo di mantenere gli esempi più chiari, nella stampa dell'output in questa sezione il percorso viene sostituito da ...

All'interno dell'Interprete

Molti sviluppatori Python lavorano con l'interprete interattivo mentre sviluppano versioni preliminari di moduli perchè possono fare esperimenti in modo maggiormente interattivo senza il ciclo di salva/esegui/ripeti necessario quando si creano script a sè stanti. Per eseguire il debugger all'interno dell'interprete interattivo si usa run() oppure runeval().

$ python
Python 2.6.6 (r266:84292, Sep 15 2010, 15:52:39)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pdb_script
>>> import pdb
>>> pdb.run('pdb_script.MyObj(5).go()')
> (1)()
(Pdb)

Il parametro di run() è una espressione stringa che possa essere valutata dall'interprete Python. Il debugger la analizzerà, quindi metterà in pausa l'esecuzione appena prima che venga valutata la prima espressione. Si possono usare i comandi del debugger sotto descritti per spostarsi e controllare l'esecuzione.

Dall'interno del Proprio Programma

Entrambi gli esempi precedenti presumevano che si volesse far partire il debugger all'inizio del proprio programma. Per un processo di lunga esecuzione, dove il problema si manifesta molto più tardi nell'esecuzione del programma, sarebbe molto più conveniente far partire il debugger all'interno del proprio programma usando set_trace().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import pdb

class MyObj(object):

    def __init__(self, num_loops):
        self.count = num_loops

    def go(self):
        for i in range(self.count):
            pdb.set_trace()
            print i
        return

if __name__ == '__main__':
    MyObj(5).go()

La riga 16 dello script di esempio fa attivare il debugger a quel punto dell'esecuzione

$ python ./pdb_set_trace.py
> .../pdb_set_trace.py(17)go()
-> print i
(Pdb)

set_trace() è semplicemente una funzione di Python, quindi si può chiamare in qualsiasi punto del proprio programma. Questo fa sì che si possa entrare nel debugger in base a delle condizioni nel programma, anche da un gestore di eccezioni o attraverso uno specifico ramo di una istruzione di controllo.

Dopo un Fallimento

Eseguire il debug di un fallimento dopo che un programma termina viene chiamato debug post-mortem. pdb supporta questo tipo di debug tramite le funzioni pm() e post_mortem().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

class MyObj(object):

    def __init__(self, num_loops):
        self.count = num_loops

    def go(self):
        for i in range(self.num_loops):
            print i
        return

Ecco che il nome dell'attributo non corretto nella riga 13 provoca una eccezione AttributeError, causando l'interruzione dell'esecuzione. pm() cerca un traceback attivo e fa partire il debugger al punto nella chiamata dello stack laddove si è verificata l'eccezione.

$ python
Python 2.6.6 (r266:84292, Sep 15 2010, 15:52:39)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pdb_post_mortem import MyObj
>>> MyObj(5).go()
Traceback (most recent call last):
  File "", line 1, in 
  File "pdb_post_mortem.py", line 13, in go
    for i in range(self.num_loops):
AttributeError: 'MyObj' object has no attribute 'num_loops'
>>> import pdb
>>> pdb.pm()
> /home/robby/pydev/pymotw-it/dumpscripts/pdb_post_mortem.py(13)go()
-> for i in range(self.num_loops):
(Pdb)

Controllare il Debugger

Si interagisce con il debugger usando un piccolo linguaggio di comando che consente di spostarsi attraverso le chiamate dello stack, esaminare e modificare i valori delle variabili, e controllare come il debugger esegue il proprio programma. Il debugger interattivo usa readline per accettare comandi. Per rieseguire il comando precedente si batte Invio inserendo una riga vuota, a meno che non sia un comando list.

Spostarsi nello Stack di Esecuzione

In qualsiasi punto mentre il debugger è in esecuzione, si può usare where (forma abbreviata w) per scoprire esattamente quale riga è in esecuzione e in che punto dello stack di chiamata ci si trova. In questo caso alla riga 17 nel metodo go() del modulo pdb_set_trace.py.

$ python pdb_set_trace.py
> .../pdb_set_trace.py(17)go()
-> print i
(Pdb) where
  .../pdb_set_trace.py(21)()
-> MyObj(5).go()
> .../pdb_set_trace.py(17)go()
-> print i
(Pdb)

Per aggiungere ulteriore contesto attorno alla locazione corrente, si usa list (l).

(Pdb) list
 12             self.count = num_loops
 13
 14         def go(self):
 15             for i in range(self.count):
 16                 pdb.set_trace()
 17  ->             print i
 18             return
 19
 20     if __name__ == '__main__':
 21         MyObj(5).go()
[EOF]
(Pdb)

Nella modalità predefinita vengono elencate 11 righe attorno a quella corrente (cinque prima e cinque dopo). L'uso di list con un singolo parametro numerico fa visualizzare le 11 righe attorno alla riga passata come parametro invece che quella corrente.

(Pdb) list 14
  9     class MyObj(object):
 10
 11         def __init__(self, num_loops):
 12             self.count = num_loops
 13
 14         def go(self):
 15             for i in range(self.count):
 16                 pdb.set_trace()
 17  ->             print i
 18             return
 19

Se list riceve due parametri, essi vengono interpretati come la prima ed ultima riga da visualizzare.

(Pdb) list 5, 19
  5     #
  6
  7     import pdb
  8
  9     class MyObj(object):
 10
 11         def __init__(self, num_loops):
 12             self.count = num_loops
 13
 14         def go(self):
 15             for i in range(self.count):
 16                 pdb.set_trace()
 17  ->             print i
 18             return
 19

Per spostarsi tra i frame all'interno dello stack corrente si usa up (abbreviato u) per spostarsi verso i frame più vecchi dello stack e down (abbreviato d) per spostarsi verso frame più recenti.

(Pdb) up
> .../pdb_set_trace.py(21)()
-> MyObj(5).go()

(Pdb) down
> .../pdb_set_trace.py(17)go()
-> print i

Ogni volta che ci si sposta su o giù il debugger stampa la locazione corrente con lo stesso formato del comando where.

Esaminare le Variabili nello Stack

Ogni frame nello stack mantiene un insieme di variabili, compresi i valori locali alla funzione in esecuzione ed informazioni di stato globale. pdb fornisce diversi modi per esaminare il contenuto di queste variabili.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import pdb

def recursive_function(n=5, output='to be printed'):
    if n > 0:
        recursive_function(n-1)
    else:
        pdb.set_trace()
        print output
    return

if __name__ == '__main__':
    recursive_function()

Il comando args (abbreviato a) stampa tutti i parametri della funzione attiva nel frame corrente. Questo esempio usa anche una funzione ricorsiva per mostrare l'aspetto di uno stack più profondo quando viene stampato da where

$ python pdb_function_arguments.py
> .../pdb_function_arguments.py(14)recursive_function()
-> return
(Pdb) where
  .../pdb_function_arguments.py(17)()
-> recursive_function()
  .../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)
  .../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)
  .../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)
  .../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)
  .../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)
> .../pdb_function_arguments.py(14)recursive_function()
-> return

(Pdb) args
n = 0
output = to be printed

(Pdb) up
> .../pdb_function_arguments.py(11)recursive_function()
-> recursive_function(n-1)

(Pdb) args
n = 1
output = to be printed

(Pdb)

Il comando p valuta una espressione passata come parametro e ne stampa il risultato. Si può anche usare l'istruzione Python print, ma la stessa viene passata attraverso l'interprete per essere eseguita piuttosto che essere eseguita come comando nel debugger.

(Pdb) p n
1

(Pdb) print n
1

Alla stessa stregua, quando si fa precedere una espressione con ! essa viene passata all'interprete Python per essere valutata. Si può usare questa caratteristica per eseguire istruzioni Python arbitrarie, incluso la modifica di variabili. Questo esempio modifica il valore di output prima di consentire al debugger di continuare l'esecuzione del programma. L'istruzione successiva dopo la chiamata di set_trace() stampa il valore di output, mostrando il valore modificato.

$ python pdb_function_arguments.py
> .../pdb_function_arguments.py(14)recursive_function()
-> print output

(Pdb) !output
'to be printed'

(Pdb) !output='valore cambiato'

(Pdb) continue
valore cambiato

Per valori più complessi tipo grandi strutture o strutture nidificate di dati, si usa pp ("pretty print"). Questo programma legge parecchie righe di testo da un file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import pdb

with open('lorem.txt', 'rt') as f:
    lines = f.readlines()

pdb.set_trace()

La stampa della variabile lines con p risulta difficile da leggere perchè le righe vanno a capo in modo poco lineare. pp usa pprint per formattare il valore per una stampa pulita.

$ python pdb_pp.py
--Return--
> .../pdb_pp.py(12)()->None
-> pdb.set_trace()
(Pdb) p lines
['Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec\n', 'egestas, enim
et consectetuer ullamcorper, lectus ligula rutrum leo, a\n', 'elementum elit tortor
eu quam.\n']

(Pdb) pp lines
['Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec\n',
 'egestas, enim et consectetuer ullamcorper, lectus ligula rutrum leo, a\n',
 'elementum elit tortor eu quam.\n']

(Pdb)

Passare Attraverso il Proprio Programma

Oltre allo spostamento su e giù all'interno dello stack di chiamata quando il programma è in pausa, si può anche avanzare attraverso l'esecuzione del programma oltre il punto dove entra il debugger.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import pdb

def f(n):
    for i in range(n):
        j = i * n
        print i, j
    return

if __name__ == '__main__':
    pdb.set_trace()
    f(5)

Si usa step per eseguire la riga corrente, quindi interrompere al successivo punto di esecuzione - sia che si tratti della prima istruzione all'interno di una funzione che si sta chiamando oppure alla riga successiva della funzione corrente.

$ python pdb_step.py
> .../pdb_step.py(17)()
-> f(5)

L'interprete va in pausa alla chiamata di set_trace() e cede il controllo al debugger. Il primo step fa sì che l'esecuzione entri in f().

(Pdb) step
--Call--
> .../pdb_step.py(9)f()
-> def f(n):

Uno step successivo sposta l'esecuzione all'interno di f() ed inizia il ciclo.

(Pdb) step
> .../pdb_step.py(10)f()
-> for i in range(n):

Con un ulteriore step ci si sposta alla prima riga all'interno del ciclo dove viene definita j.

(Pdb) step
> .../pdb_step.py(11)f()
-> j = i * n
(Pdb) p i
0

Il valore di i è 0, quindi dopo un ulteriore spostamento il valore di j dovrebbe essere anch'esso 0.

(Pdb) step
> .../pdb_step.py(12)f()
-> print i, j

(Pdb) p j
0

(Pdb)

Procedendo di una riga alla volta in questo modo diventa noioso se il codice da coprire prima di arrivare al punto in cui si verifica l'errore è molto, oppure se la stessa funzione viene chiamata ripetutamente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import pdb

def calc(i, n):
    j = i * n
    return j

def f(n):
    for i in range(n):
        j = calc(i, n)
        print i, j
    return

if __name__ == '__main__':
    pdb.set_trace()
    f(5)

In questo esempio, non c'è nulla di sbagliato in calc(), quindi passarci attraverso ogni volta che viene chiamata nel ciclo in f() oscura l'output utile mostrando tutte le righe di calc() così come sono eseguite.

$ python pdb_next.py
> .../pdb_next.py(21)()
-> f(5)
(Pdb) step
--Call--
> .../pdb_next.py(13)f()
-> def f(n):

(Pdb) step
> .../pdb_next.py(14)f()
-> for i in range(n):

(Pdb) step
> .../pdb_next.py(15)f()
-> j = calc(i, n)

(Pdb) step
--Call--
> .../pdb_next.py(9)calc()
-> def calc(i, n):

(Pdb) step
> .../pdb_next.py(10)calc()
-> j = i * n

(Pdb) step
> .../pdb_next.py(11)calc()
-> return j

(Pdb) step
--Return--
> .../pdb_next.py(11)calc()->0
-> return j

(Pdb) step
> .../pdb_next.py(16)f()
-> print i, j

(Pdb) step
0 0

Il comando next è come step, ma non entra nelle funzioni chiamate dall'istruzione in esecuzione. In effetti passa direttamente attraverso la chiamata della funzione fino alla prossima istruzione nella funzione corrente in una singola operazione

> .../pdb_next.py(14)f()
-> for i in range(n):
(Pdb) step
> .../pdb_next.py(15)f()
-> j = calc(i, n)

(Pdb) next
> .../pdb_next.py(16)f()
-> print i, j

(Pdb)

Il comando until è come next, eccetto che continua esplicitamente fino a che l'esecuzione raggiunge una riga nella stessa funzione il cui numero è più alto del valore corrente. Significa che, ad esempio, until può essere usato per passare oltre la fine di un ciclo.

$ python pdb_next.py
> .../pdb_next.py(21)()
-> f(5)
(Pdb) step
--Call--
> .../pdb_next.py(13)f()
-> def f(n):

(Pdb) step
> .../pdb_next.py(14)f()
-> for i in range(n):

(Pdb) step
> .../pdb_next.py(15)f()
-> j = calc(i, n)

(Pdb) next
> .../pdb_next.py(16)f()
-> print i, j

(Pdb) until
0 0
1 5
2 10
3 15
4 20
> .../pdb_next.py(17)f()
-> return

(Pdb)

Prima che fosse eseguito until, la riga corrente era 16, l'ultima riga del ciclo. Dopo until, l'esecuzione era sulla riga 17, ed il ciclo è stato esaurito.

return è un'altra scorciatoia per superare parti di una funzione. Continua l'esecuzione fino a che la funzione sta per eseguire una istruzione return, quindi va in pausa. Questo dà il tempo di verificare il valore da restituire prima che la funzioni termini.

$ python pdb_next.py
> .../pdb_next.py(21)()
-> f(5)
(Pdb) step
--Call--
> .../pdb_next.py(13)f()
-> def f(n):

(Pdb) step
> .../pdb_next.py(14)f()
-> for i in range(n):

(Pdb) return
0 0
1 5
2 10
3 15
4 20
--Return--
> .../pdb_next.py(17)f()->None
-> return

(Pdb)

Punti di Interruzione (Breakpoint)

La dimensione dei programmi può raggiungere un livello oltre il quale anche l'uso di next e until diventa lento e macchinoso. Una migliore soluzione rispetto al passare attraverso sezioni di programma manualmente, è quella di lasciare che il programma venga eseguito normalmente fino al punto in cui si vuole che il debugger lo interrompa. Si può usare set_trace() per far partire il debugger, ma la cosa funziona solo se c'è un solo punto nel quale si vuole che il programma venga interrotto. E' più conveniente eseguire il programma tramite il debugger, indicando anticipatamente al debugger dove si vuole che il programma venga interrotto con i punti di interruzione (breakpoint). Il debugger monitorizza il programma, e ne mette in pausa l'esecuzione quando raggiunge la locazione descritta da un punto di interruzione, prima che la riga di codice venga eseguita.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

def calc(i, n):
    j = i * n
    print 'j =', j
    if j > 0:
        print 'Positive!'
    return j

def f(n):
    for i in range(n):
        print 'i =', i
        j = calc(i, n)
    return

if __name__ == '__main__':
    f(5)

Ci sono parecchie opzioni per il comando break che si usa per impostare i punti di interruzione. Si può specificare il numero di riga, file e funzione dove l'esecuzione dovrebbe essere messa in pausa. Per impostare un punto di interruzione in una specifica riga del file corrente, si usa break numero_riga:

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break 11
Breakpoint 1 at .../pdb_break.py:11

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb)

Il comando continue dice al debugger di riprendere l'esecuzione del programma fino al successivo punto di interruzione. In questo caso il programma viene eseguito fino alla prima iterazione del ciclo for in f(), e viene messo in pausa all'interno di calc() durante la seconda iterazione.

I punti di interruzione possono anche essere impostati alla prima riga di una funzione specificando il nome della funzione invece che un numero di riga. Questo esempio mostra cosa succede se un punto di interruzione viene aggiunto alla funzione calc().

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:7

(Pdb) continue
i = 0
> .../pdb_break.py(8)calc()
-> j = i * n

(Pdb) where
  .../pdb_break.py(21)()
-> f(5)
  .../pdb_break.py(17)f()
-> j = calc(i, n)
> .../pdb_break.py(8)calc()
-> j = i * n

(Pdb)

Per specificare un punto di interruzione in un altro file, si prefissa il parametro di riga o funzione con un nome di file.

1
2
3
4
5
6
7
8
9
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

from pdb_break import f

f(5)

Qui viene impostato un punto di interruzione per la riga 11 di pdb_break.py dopo aver fatto partire il programma principale pdb_break_remote.py.

$ python -m pdb pdb_break_remote.py
> .../pdb_break_remote.py(7)()
-> from pdb_break import f
(Pdb) break pdb_break.py:11
Breakpoint 1 at .../pdb_break.py:11

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb)

Il nome del file può essere un nome completo del file sorgente oppure un percorso relativo ad un file disponibile in sys.path.

Per elencare i punti di interruzione attualmente impostati, si usa break senza parametri. L'output comprende il nome del file e d il numero riga di ogni punto di interruzione, insieme al numero di volte in cui esso è stato rilevato.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break 11
Breakpoint 1 at .../pdb_break.py:11

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:11

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb/pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb) continue
Positive!
i = 2
j = 10
> .../pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:11
        breakpoint already hit 2 times

(Pdb)

Gestire i Punti di Interruzione

Ad ogni nuovo punto di interruzione aggiunto, viene assegnato un identificatore numerico. Questi identificatori sono usati per abilitare, disabilitare e rimuovere interattivamente i punti di interruzione.

Per disabilitare un punto di interruzione si usa disable che indica al debugger di non fermarsi quando quella riga viene raggiunta. Il punto di interruzione non viene dimenticato, ma viene ignorato.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:7

(Pdb) break 11
Breakpoint 2 at .../pdb_break.py:11

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:7
2   breakpoint   keep yes   at .../pdb_break.py:11

(Pdb) disable 1

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep no    at .../pdb_break.py:7
2   breakpoint   keep yes   at .../pdb_break.py:11

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb)

Le sessione di debug che segue imposta due punti di interruzione nel programma, poi ne disabilita uno. Il programma viene eseguito fino a che viene rilevato il punto di interruzione rimanente, successivamente l'altro punto di interruzione viene riattivato con enable prima di riprendere l'esecuzione.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:7

(Pdb) break 16
Breakpoint 2 at .../pdb_break.py:16

(Pdb) disable 1

(Pdb) continue
> .../pdb_break.py(16)f()
-> print 'i =', i

(Pdb) list
 11             print 'Positive!'
 12         return j
 13
 14     def f(n):
 15         for i in range(n):
 16 B->         print 'i =', i
 17             j = calc(i, n)
 18         return
 19
 20     if __name__ == '__main__':
 21         f(5)

(Pdb) continue
i = 0
j = 0
> .../pdb_break.py(16)f()
-> print 'i =', i

(Pdb) list
 11             print 'Positive!'
 12         return j
 13
 14     def f(n):
 15         for i in range(n):
 16 B->         print 'i =', i
 17             j = calc(i, n)
 18         return
 19
 20     if __name__ == '__main__':
 21         f(5)

(Pdb) p i
1

(Pdb) enable 1

(Pdb) continue
i = 1
> .../pdb_break.py(8)calc()
-> j = i * n

(Pdb) list
  3     #
  4     # Copyright (c) 2010 Doug Hellmann.  All rights reserved.
  5     #
  6
  7 B   def calc(i, n):
  8  ->     j = i * n
  9         print 'j =', j
 10         if j > 0:
 11             print 'Positive!'
 12         return j
 13

(Pdb)

Le righe prefissate da B nell'output di list mostrano dove sono impostati i punti di interruzione nel programma (alle righe 9 e 18).

Per eliminare definitivamente un punto di interruzione si usa clear.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:7

(Pdb) break 11
Breakpoint 2 at .../pdb_break.py:11

(Pdb) break 16
Breakpoint 3 at .../pdb_break.py:16

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:7
2   breakpoint   keep yes   at .../pdb_break.py:11
3   breakpoint   keep yes   at .../pdb_break.py:16

(Pdb) clear 2
Deleted breakpoint 2

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:7
3   breakpoint   keep yes   at .../pdb_break.py:16

(Pdb)

I punti di interruzione rimanenti mantengono i loro identificativi originali e non sono rinumerati.

Punti di Interruzione Temporanei

Un punto di interruzione temporaneo viene automaticamente eliminato dopo che viene raggiunto la prima volta. L'uso di un punto di interruzione temporaneo consente di raggiungere rapidamente un punto specifico nel flusso del programma, proprio come un normale punto di interruzione, ma, visto che viene immediatamente eliminato non interferirà qualora quella parte del programma venga eseguita ripetutamente.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) tbreak 11
Breakpoint 1 at .../pdb_break.py:11

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
Deleted breakpoint 1
> .../pdb_break.py(11)calc()
-> print 'Positive!'

(Pdb) break

(Pdb) continue
Positive!
i = 2
j = 10
Positive!
i = 3
j = 15
Positive!
i = 4
j = 20
Positive!
The program finished and will be restarted
> .../pdb_break.py(7)()
-> def calc(i, n):

(Pdb)

Dopo che il programma raggiunge la riga 11 la prima volta, il punto di interruzione viene eliminato e l'esecuzione non viene più interrotta fino alla fine del programma.

Punti di Interruzione Condizionali

Si possono applicare regole ai punti di interruzione in modo che l'esecuzione venga messa in pausa solo al verificarsi di certe condizioni. L'uso di punti di interruzione condizionali consente di avere un controllo più rifinito su come il debugger mette in pausa il proprio programma, rispetto all'abilitare e disabilitare manualmente i punti di interruzione.

I punti di interruzione condizionali possono essere impostati in due modi. Il primo è specificare la condizione quando si imposta il punto di interruzione usando break.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break 9, j>0
Breakpoint 1 at .../pdb_break.py:9

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:9
        stop only if j>0

(Pdb) continue
i = 0
j = 0
i = 1
> .../pdb_break.py(9)calc()
-> print 'j =', j

(Pdb)

Il parametro della condizione deve essere una espressione che usi valori visibili nello stack frame dove viene definito il punto di interruzione. Se l'espressione valutata è vera, l'esecuzione viene messa in pausa al punto di interruzione.

Una condizione può anche applicarsi ad un punto di interruzione esistente tramite il comando condition. I parametri sono l'identificativo del punto di interruzione e l'espressione.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break 9
Breakpoint 1 at .../pdb_break.py:9

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:9

(Pdb) condition 1 j>0

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:9
        stop only if j>0

(Pdb)

Ignorare i Punti di Interruzione

Per i programmi che hanno molti cicli o chiamate ricorsive alla stessa funzione il debug è spesso facilitato "saltando avanti" nell'esecuzione, invece di guardare tutte le chiamate o punti di interruzione. Il comando ignore indica al debugger di superare un punto di interruzione senza fermarsi. Ogni volta che l'esecuzione rileva il punto di interruzione, decrementa il contatore di ignore. Quando il contatore raggiunge lo zero, il punto di interruzione viene riattivato.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break 17
Breakpoint 1 at .../pdb_break.py:17

(Pdb) continue
i = 0
> .../pdb_break.py(17)f()
-> j = calc(i, n)

(Pdb) next
j = 0
> .../pdb_break.py(15)f()
-> for i in range(n):

(Pdb) ignore 1 2
Will ignore next 2 crossings of breakpoint 1.

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:17
        ignore next 2 hits
        breakpoint already hit 1 time

(Pdb) continue
i = 1
j = 5
Positive!
i = 2
j = 10
Positive!
i = 3
> .../pdb_break.py(17)f()
-> j = calc(i, n)

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:17
        breakpoint already hit 4 times

Il punto di interruzione viene riattivato immediatamente se si reimposta esplicitamente il contatore di ignore a zero.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break 17
Breakpoint 1 at .../pdb_break.py:17

(Pdb) ignore 1 2
Will ignore next 2 crossings of breakpoint 1.

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:17
        ignore next 2 hits

(Pdb) ignore 1 0
Will stop next time breakpoint 1 is reached.

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:17

Attivare Azioni in un Punto di Interruzione

Oltre al modo puramente interattivo, pdb supporta un basico sistema di scripting. Tramite commands, si può definire una serie di comandi dell'interprete, comprese istruzioni Python, da eseguire quando uno specifico punto di interruzione viene raggiunto. Dopo l'esecuzione di commands con il numero del punto di interruzione come parametro, il prompt del debugger cambia in (com). I comandi si digitano uno alla volta, quindi si termina l'elenco con end per salvare lo script e ritornare al prompt principale del debugger.

$ python -m pdb pdb_break.py
> .../pdb_break.py(7)()
-> def calc(i, n):
(Pdb) break 9
Breakpoint 1 at .../pdb_break.py:9

(Pdb) commands 1
(com) print 'debug i =', i
(com) print 'debug j =', j
(com) print 'debug n =', n
(com) end

(Pdb) continue
i = 0
debug i = 0
debug j = 0
debug n = 5
> .../pdb_break.py(9)calc()
-> print 'j =', j

(Pdb) continue
j = 0
i = 1
debug i = 1
debug j = 5
debug n = 5
> .../pdb_break.py(9)calc()
-> print 'j =', j

(Pdb)

Questa caratteristica è utile in particolar modo per il debug di codice che usa molte variabili e strutture dati, visto che si può far stampare al debugger tutti i valori automaticamente, invece di farlo manualmente ogni volta che si raggiunge il punto di interruzione.

Modificare il Flusso di Esecuzione

Il comando jump consente di alterare il flusso del proprio programma in fase di esecuzione, senza modificare il codice. Si può saltare avanti per evitare l'esecuzione di una parte di codice, o indietro per rieseguirlo. Il programma di esempio genera un elenco di numeri.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

def f(n):
    result = []
    j = 0
    for i in range(n):
        j = i * n + j
        j += n
        result.append(j)
    return result

if __name__ == '__main__':
    print f(5)

Quando lo script viene eseguito senza interferenze, il suo output è una sequenza di numeri crescenti divisibili per 5.

$ python pdb_jump.py

[5, 15, 30, 50, 75]

Saltare Avanti

Il salto in avanti sposta il punto di esecuzione oltre la locazione corrente senza valutare alcuna delle istruzioni in mezzo. Il saltare la riga 13 nell'esempio seguente, non fa incrementare il valore di j e tutti i valori successivi che dipendono da esso sono più piccoli.

$ python -m pdb pdb_jump.py
> .../pdb_jump.py(7)()
-> def f(n):
(Pdb) break 12
Breakpoint 1 at .../pdb_jump.py:12

(Pdb) continue
> .../pdb_jump.py(12)f()
-> j += n

(Pdb) p j
0

(Pdb) step
> .../pdb_jump.py(13)f()
-> result.append(j)

(Pdb) p j
5

(Pdb) continue
> .../pdb_jump.py(12)f()
-> j += n

(Pdb) jump 13
> .../pdb_jump.py(13)f()
-> result.append(j)

(Pdb) p j
10

(Pdb) disable 1

(Pdb) continue
[5, 10, 25, 45, 70]

The program finished and will be restarted
> .../pdb_jump.py(7)()
-> def f(n):
(Pdb)

Saltare Indietro

Con jump si può spostare l'esecuzione del programma anche ad un istruzione che è già stata eseguita, per rieseguirla. Qui il valore di j viene incrementato una volta in più, quindi i numeri nella sequenza risultante sono tutti più grandi di quello che avrebbero dovuto essere altrimenti.

$ python -m pdb pdb_jump.py
> .../pdb_jump.py(7)()
-> def f(n):
(Pdb) break 13
Breakpoint 1 at .../pdb_jump.py:13

(Pdb) continue
> .../pdb_jump.py(13)f()
-> result.append(j)

(Pdb) p j
5

(Pdb) jump 12
> .../pdb_jump.py(12)f()
-> j += n

(Pdb) continue
> .../pdb_jump.py(13)f()
-> result.append(j)

(Pdb) p j
10

(Pdb) disable 1

(Pdb) continue
[10, 20, 35, 55, 80]

The program finished and will be restarted
> .../pdb_jump.py(7)()
-> def f(n):
(Pdb)

Salti Illegali

Saltare dentro o fuori da certe istruzioni di controllo di flusso è pericoloso o indefinito, quindi il debugger non lo consente.

 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
31
32
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

def f(n):
    if n < 0:
        raise ValueError('Invalid n: %s' % n)
    result = []
    j = 0
    for i in range(n):
        j = i * n + j
        j += n
        result.append(j)
    return result


if __name__ == '__main__':
    try:
        print f(5)
    finally:
        print 'Always printed'

    try:
        print f(-5)
    except:
        print 'There was an error'
    else:
        print 'There was no error'

    print 'Last statement'

Si può saltare all'interno di una funzione, ma in questo caso i parametri non sono definiti ed è improbabile che il codice funzioni.

$ python -m pdb pdb_no_jump.py
> .../pdb_no_jump.py(7)()
-> def f(n):
(Pdb) break 21
Breakpoint 1 at .../pdb_no_jump.py:21

(Pdb) jump 8
> .../pdb_no_jump.py(8)()
-> if n < 0:

(Pdb) p n
*** NameError: NameError("name 'n' is not defined",)

(Pdb) args

(Pdb)

Non si può saltare nel mezzo di un blocco di codice tipo un ciclo for od una istruzione try:except.

$ python -m pdb pdb_no_jump.py
> .../pdb_no_jump.py(7)()
-> def f(n):
(Pdb) break 21
Breakpoint 1 at .../pdb_no_jump.py:21

(Pdb) continue
> .../pdb_no_jump.py(21)()
-> print f(5)

(Pdb) jump 26
*** Jump failed: can't jump into the middle of a block

(Pdb)

Il codice in un blocco finally deve essere esguito per intero, quindi non si può saltare fuori da quel blocco.

$ python -m pdb pdb_no_jump.py
> .../pdb_no_jump.py(7)()
-> def f(n):
(Pdb) break 23
Breakpoint 1 at .../pdb_no_jump.py:23

(Pdb) continue
[5, 15, 30, 50, 75]
> .../pdb_no_jump.py(23)()
-> print 'Always printed'

(Pdb) jump 25
*** Jump failed: can't jump into or out of a 'finally' block

(Pdb)

La restrizione base è che il salto è confinato al frame inferiore della chiamata dello stack. Se ci si sposta in su nello stack per esaminare le variabili, a quel punto non si può modificare il flusso di esecuzione.

$ python -m pdb pdb_no_jump.py
> .../pdb_no_jump.py(7)()
-> def f(n):
(Pdb) break 11
Breakpoint 1 at .../pdb_no_jump.py:11

(Pdb) continue
> .../pdb_no_jump.py(11)f()
-> j = 0

(Pdb) where
  /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/bdb.py(379)run()
-> exec cmd in globals, locals
  (1)()
  .../pdb_no_jump.py(21)()
-> print f(5)
> .../pdb_no_jump.py(11)f()
-> j = 0

(Pdb) up
> .../pdb_no_jump.py(21)()
-> print f(5)

(Pdb) jump 25
*** You can only jump within the bottom frame

(Pdb)

Riavviare il programma

Quando il debugger raggiunge la fine del proprio programma, lo fa ripartire automaticamente, ma si può farlo ripartire esplicitamente senza lasciare il debugger e perdere i propri punti di interruzione od altre impostazioni.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import sys

def f():
    print 'Command line args:', sys.argv
    return

if __name__ == '__main__':
    f()

Se si esegue il programma di cui sopra fino alla fine all'interno del debugger viene stampato il nome del file di script, visto che non sono stati forniti altri parametri da riga di comando.

$ python -m pdb pdb_run.py
> .../pdb_run.py(7)()
-> import sys
(Pdb) continue

Command line args: ['pdb_run.py']
The program finished and will be restarted
> .../pdb_run.py(7)()
-> import sys

(Pdb)

Il programma può essere riavviato usando run. I parametri passati a run sono analizzati con shlex e passati al programma come se fossero parametri da riga di comando, in modo che si possa riavviare il programma con parametri differenti.

(Pdb) run a b c "this is a long value"
Restarting pdb_run.py with arguments:
        a b c this is a long value
> .../pdb_run.py(7)()
-> import sys

(Pdb) continue
Command line args: ['pdb_run.py', 'a', 'b', 'c', 'this is a long value']
The program finished and will be restarted
> .../pdb_run.py(7)()
-> import sys

(Pdb)

run può essere usato anche in qualsiasi altro punto per riavviare il programma-

$ python -m pdb pdb_run.py
> .../pdb_run.py(7)()
-> import sys
(Pdb) break 10
Breakpoint 1 at .../pdb_run.py:10

(Pdb) continue
> .../pdb_run.py(10)f()
-> print 'Command line args:', sys.argv

(Pdb) run one two three
Restarting pdb_run.py with arguments:
        one two three
> .../pdb_run.py(7)()
-> import sys

(Pdb)

Personalizzare il Debugger con Alias

Si può evitare di digitare comandi complessi ripetutamente usando alias per definire una scorciatoia. L'espansione dell'alias si applica alla prima parola di ogni comando. Il corpo dell'alias può essere costituito da qualsiasi comando che sia legale digirare al prompt del debugger, inclusi altri comandi del debugger ed espressioni Python pure. E' concessa che la recursione nella definizione degli alias, quindi un alias ne può chiamare un altro.

$ python -m pdb pdb_function_arguments.py
> .../pdb_function_arguments.py(7)()
-> import pdb
(Pdb) break 10
Breakpoint 1 at .../pdb_function_arguments.py:10

(Pdb) continue
> .../pdb_function_arguments.py(10)recursive_function()
-> if n > 0:

(Pdb) pp locals().keys()
['output', 'n']

(Pdb) alias pl pp locals().keys()

(Pdb) pl
['output', 'n']

Eseguire alias senza parametri mostra l'elenco degli alias definiti. Un parametro singolo viene considerato come il nome dell'alias e la sua definizione viene stampata.

(Pdb) alias
pl = pp locals().keys()

(Pdb) alias pl
pl = pp locals().keys()
(Pdb)

I parametri per gli alias sono referenziati con %n, dove n rappresenta un numero che indica la posizione del parametro, a partire da 1. Per utilizzare tutti i parametri si usa %*.

$ python -m pdb pdb_function_arguments.py
> .../pdb_function_arguments.py(7)()
-> import pdb
(Pdb) alias ph !help(%1)

(Pdb) ph locals
Help on built-in function locals in module __builtin__:

locals(...)
    locals() -> dictionary

    Update and return a dictionary containing the current scope's local variables.

Si elimina la definizione di un alias con unalias.

(Pdb) unalias ph

(Pdb) ph locals
*** SyntaxError: invalid syntax (, line 1)

(Pdb)

Salvare le Impostazioni di Configurazione

Il debug di un programma consta di molte ripetizioni; eseguire il codice, osservarne il risultato, aggiustare il codice o gli input, e rieseguirlo. pdb tenta di abbattere il numero di ripetizioni che occorrono per controllare il debug, per consentire al programmatore di concentrarsi sul codice, invece che sul debugger. Per aiutare a ridurre il numero di volte nelle quali si impartiscono gli stessi comandi al debugger pdb consente di salvare la configurazione usando file di testo letti ed interpretati in partenza.

Il file ~/.pdbrc viene letto per primo, consentendo all'utente di impostare le preferenze globali personali per tutte le sue sessioni di debug. Poi ./.pdbrc viene letto dalla directory di lavoro corrente, in modo che si possano impostare preferenze locali per un particolare progetto.

$ cat ~/.pdbrc
# Show python help
alias ph !help(%1)
# Overridden alias
alias redefined p 'home definition'

$ cat .pdbrc
# Breakpoints
break 10
# Overridden alias
alias redefined p 'local definition'

$ python -m pdb pdb_function_arguments.py
Breakpoint 1 at .../pdb_function_arguments.py:10
> .../pdb_function_arguments.py(7)()
-> import pdb
(Pdb) alias
ph = !help(%1)
redefined = p 'local definition'

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_function_arguments.py:10

(Pdb)

Qualsiasi configurazione di comandi che possono essere digitati al prompt del debugger può essere salvata in uno dei file di startup, ma con la maggior parte dei comandi che controllano l'esecuzione (continue, jump. ecc.) non è possibile. L'eccezione è run, il che vuole dire che si possono impostare i parametri di riga di comando per una sessione di debug in ./.pdbrc in modo che siano consistenti attraverso diverse esecuzioni.

Vedere anche:

pdb
La documentazione della libreria standard per questo modulo.
readline
Libreria per la modifica del prompt interattivo
cmd
Costruisce programmi interattivi
shlex
Analizzatore della riga di comando della shell