Quand il s'agit de déboguer du code python la plus part des gens utilisent un print "debug" bien placé pour savoir si on passe à un endroit ou un print "ma_variable = '%s'" % ma_variable pour connaitre la valeur d'une variable dans le flux d'exécution.
Pourtant python dispose d'un débogueur intégré très puissant: pdb. Que ceux qui sont allergiques à gdb, le débogueur C/C++, soient rassurés: bien qui pdb ressemble à bien des égards à gdb c'est un débogueur python pour python. Il est à la fois simple et très puissant.
Nous allons voir deux cas simples d'utilisation qui seront utiles, je l'espère, aux développeurs expérimentés comme aux développeurs occasionnels. Pour l'exemple nous utiliserons le script suivant :
#!/usr/bin/env python
def func1(i):
if i == 0:
raise Exception("Exception in func1")
def func2(i):
func1(i)
def func3(i):
func2(i)
if __name__ == '__main__':
func3(0)
La traceback surprise
Vous développez ou utilisez votre logiciel préféré et BOUM une traceback :
Traceback (most recent call last):
File "test.py", line 14, in <module>
func3(0)
File "test.py", line 11, in func3
func2(i)
File "test.py", line 8, in func2
func1(i)
File "test.py", line 5, in func1
raise Exception("Exception in func1")
Exception: Exception in func1
Vous n'avez pas accès au code en écriture ou le cas est trop complexe pour être géré avec des print bien placés ; pdb va vous aider.
Depuis python 2.5 vous pouvez exécuter des modules python comme des scripts.
On va donc lancer le script en utilisant pdb :
python -m pdb test.py
On se retrouve dans pdb :
[chicha@grogro Bureau]$ python -m pdb test.py > /home/chicha/Bureau/test.py(3)<module>() -> def func1(i):
A ce stade le programme n'a toujours pas été lancé. On le lance via la commande pdb continu (c). Le programme entre en mode "post mortem" dès qu'il se prend une exception non catchée :
Traceback (most recent call last):
File "/usr/lib64/python2.6/pdb.py", line 1276, in main
pdb._runscript(mainpyfile)
File "/usr/lib64/python2.6/pdb.py", line 1193, in _runscript
self.run(statement)
File "/usr/lib64/python2.6/bdb.py", line 366, in run
exec cmd in globals, locals
File "<string>", line 1, in <module>
File "test.py", line 14, in <module>
func3(0)
File "test.py", line 11, in func3
func2(i)
File "test.py", line 8, in func2
func1(i)
File "test.py", line 5, in func1
raise Exception("Exception in func1")
Exception: Exception in func1
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program
> /home/chicha/Bureau/test.py(5)func1()
-> raise Exception("Exception in func1")
Voilà la séance de debugging peut commencer ...
Bye bye print
Pour commencer on aimerait bien savoir où l'on est dans la pile de fonction et à quoi ressemble le code autour de là où on est. C'est le role des commandes backtrace (bt) et list (l) :
(Pdb) bt
/usr/lib64/python2.6/bdb.py(371)run()
-> sys.settrace(None)
<string>(1)<module>()
/home/chicha/Bureau/test.py(3)<module>()
-> def func1(i):
/home/chicha/Bureau/test.py(11)func3()
-> func2(i)
/home/chicha/Bureau/test.py(8)func2()
-> func1(i)
> /home/chicha/Bureau/test.py(5)func1()
-> raise Exception("Exception in func1")
(Pdb) l
1 #!/usr/bin/env python
2
3 def func1(i):
4 if i == 0:
5 -> raise Exception("Exception in func1")
6
7 def func2(i):
8 func1(i)
9
10 def func3(i):
11 func2(i)
(Pdb)
Ensuite on aimerait bien connaitre la valeur des arguments passés en paramètres, c'est le rôle de la commande args :
(Pdb) args i = 0 (Pdb)
Enfin on peut afficher la valeur de n'importe quelle variable ou expression python valide à l'endroit où l'on se trouve dans le code, en utilisant la commande print (p) :
(Pdb) p i 0 (Pdb) p 2 * 2 4 (Pdb)
pdb fournit évidemment bien plus de commandes que cela, vous pouvez par exemple :
- Monter et descendre où vous voulez dans la pile d'appels de fonctions,
- Mettre des breakpoint, conditionnels ou non, temporaires ou non,
- Associer des commandes à des breakpoints,
- Exécuter du code ligne par ligne, fonctions par fonctions,
- etc ...
Vous avez tout ce qu'il faut dans la documentation de pdb Si vous avez une question n'hésitez pas à me contacter !
Mais avant de terminer cet article je voudrai vous montrer une autre façon d'utiliser pdb :
pdb ou le debugging chirurgical
Vous avez accès au code en écriture, Vous exécutez un programme très long et vous n'avez pas envie de débugger tout, mais seulement le bout de code qui vous intéresse ? Alors pdb.set_trace() est fait pour vous !
Je reprend le code précédent, j'importe pdb et je place un petit pdb.set_trace() là ou je veux commencer à débugger :
#!/usr/bin/env python
import pdb
def func1(i):
pdb.set_trace()
if i == 0:
raise Exception("Exception in func1")
def func2(i):
func1(i)
def func3(i):
func2(i)
if __name__ == '__main__':
func3(0)
Je lance mon pogramme normalement (sans utiliser -m pdb) et pouf ! Le programme s'exécute jusqu'à la ligne pdb.set_trace(). Ensuite pdb se lance et vous avez le debuggeur à votre disposition !
Bien sûre avec mon exemple simpliste on voit mal l'intérêt de la chose. Mais dès que le code se complique avec des boucles for sur des milliers d'éléments avec un bug au 700 ième on est bien content de pouvoir faire un truc du genre :
for i in range(1000):
if i = 700:
pdb.set_trace()
...
J'espère que ça permettra aux plus réticents, que gdb a effrayé, de se lancer !
