| Scopo | Il debugger interattivo di Python |
| Versione Python | 1.4 e superiore |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per le versioni 3.x di Python
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.
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.
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)
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.
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.
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)
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
.
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
.
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)
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)
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)
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.
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.
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)
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
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.
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]
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)
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)
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)
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)
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)
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.