Recherche de site Web

Comment utiliser le débogueur Python


Introduction

Dans le développement de logiciels, le débogage est le processus de recherche et de résolution des problèmes qui empêchent le logiciel de fonctionner correctement.

Le débogueur Python fournit un environnement de débogage pour les programmes Python. Il prend en charge la définition de points d'arrêt conditionnels, le passage à travers le code source ligne par ligne, l'inspection de la pile, et plus encore.

Conditions préalables

Vous devez avoir Python 3 installé et un environnement de programmation configuré sur votre ordinateur ou serveur. Si vous n'avez pas d'environnement de programmation configuré, vous pouvez consulter les guides d'installation et de configuration pour un environnement de programmation local ou pour un environnement de programmation sur votre serveur, approprié à votre système d'exploitation (Ubuntu, CentOS, Debian, etc.).

Travailler de manière interactive avec le débogueur Python

Le débogueur Python fait partie de la distribution standard de Python sous la forme d'un module appelé pdb. Le débogueur est également extensible et est défini comme la classe Pdb. Vous pouvez lire la documentation officielle de pdb pour en savoir plus.

Info : Pour suivre l'exemple de code dans ce tutoriel, ouvrez une interface Python interactive sur votre système local en exécutant la commande python3. Vous pouvez ensuite copier, coller ou modifier les exemples en les ajoutant après l'invite >>>.

Nous commencerons par travailler avec un court programme qui a deux variables globales, une fonction qui crée une boucle imbriquée, et la construction if __name__ == '__main__': qui appellera la fonction nested_loop().

num_list = [500, 600, 700]
alpha_list = ['x', 'y', 'z']


def nested_loop():
    for number in num_list:
        print(number)
        for letter in alpha_list:
            print(letter)

if __name__ == '__main__':
    nested_loop()

Nous pouvons désormais exécuter ce programme à l'aide du débogueur Python en utilisant la commande suivante :

  1. python -m pdb looping.py

L'option de ligne de commande -m importera n'importe quel module Python pour vous et l'exécutera en tant que script. Dans ce cas, nous importons et exécutons le module pdb, que nous passons dans la commande comme indiqué ci-dessus.

En exécutant cette commande, vous recevrez la sortie suivante :

> /Users/sammy/looping.py(1)<module>()
-> num_list = [500, 600, 700]
(Pdb) 

Dans la sortie, la première ligne contient le nom du module actuel (comme indiqué avec <module>) avec un chemin d'accès, et le numéro de ligne imprimé qui suit (dans ce cas, c'est 1, mais s'il y a un commentaire ou une autre ligne non exécutable, cela pourrait être un numéro plus élevé). La deuxième ligne montre la ligne actuelle de code source qui est exécutée ici, car pdb fournit une console interactive pour le débogage. Vous pouvez utiliser la commande help pour apprendre ses commandes, et help command pour en savoir plus sur une commande spécifique. Notez que la console pdb est différente de l'interpréteur Python interactif.

Le débogueur Python redémarrera automatiquement lorsque vous atteindrez la fin de votre programme. Chaque fois que vous souhaitez quitter la console pdb, tapez la commande quit ou exit. Si vous souhaitez redémarrer explicitement un programme à un endroit quelconque dans le programme, vous pouvez le faire avec la commande run.

Utiliser le Débogueur pour naviguer dans un Programme

Lorsque vous travaillez avec des programmes dans le débogueur Python, vous êtes susceptible d'utiliser les commandes list, step et next pour naviguer dans votre code. Nous passerons en revue ces commandes dans cette section.

Dans le shell, nous pouvons taper la commande list afin d'obtenir le contexte autour de la ligne actuelle. À partir de la première ligne du programme looping.py que nous avons affichée ci-dessus — num_list=[500, 600, 700] — cela ressemblera à ce qui suit :

(Pdb) list
  1  ->	num_list = [500, 600, 700]
  2  	alpha_list = ['x', 'y', 'z']
  3  	
  4  	
  5  	def nested_loop():
  6  	    for number in num_list:
  7  	        print(number)
  8  	        for letter in alpha_list:
  9  	            print(letter)
 10  	
 11  	if __name__ == '__main__':
(Pdb) 

La ligne actuelle est indiquée par les caractères ->, qui dans notre cas est la première ligne du fichier du programme.

Puisque c'est un programme relativement court, nous recevons presque l'intégralité du programme avec la commande list. Sans fournir d'arguments, la commande list fournit 11 lignes autour de la ligne actuelle, mais vous pouvez également spécifier quelles lignes inclure, comme ceci :

(Pdb) list 3, 7
  3  	
  4  	
  5  	def nested_loop():
  6  	    for number in num_list:
  7  	        print(number)
(Pdb) 

Ici, nous avons demandé que les lignes 3 à 7 soient affichées en utilisant la commande list 3, 7.

Pour avancer à travers le programme ligne par ligne, nous pouvons utiliser step ou next:

(Pdb) step
> /Users/sammy/looping.py(2)<module>()
-> alpha_list = ['x', 'y', 'z']
(Pdb) 
(Pdb) next
> /Users/sammy/looping.py(2)<module>()
-> alpha_list = ['x', 'y', 'z']
(Pdb) 

La différence entre step et next est que step s'arrête à l'intérieur d'une fonction appelée, tandis que next exécute les fonctions appelées pour s'arrêter uniquement à la ligne suivante de la fonction actuelle. Nous pouvons voir cette différence lorsque nous travaillons avec la fonction.

La commande step itérera à travers les boucles une fois qu'elle atteindra l'exécution de la fonction, montrant exactement ce que la boucle fait, car elle imprimera d'abord un nombre avec print(number), puis passera à imprimer les lettres avec print(letter), reviendra au nombre, etc :

(Pdb) step
> /Users/sammy/looping.py(5)<module>()
-> def nested_loop():
(Pdb) step
> /Users/sammy/looping.py(11)<module>()
-> if __name__ == '__main__':
(Pdb) step
> /Users/sammy/looping.py(12)<module>()
-> nested_loop()
(Pdb) step
--Call--
> /Users/sammy/looping.py(5)nested_loop()
-> def nested_loop():
(Pdb) step
> /Users/sammy/looping.py(6)nested_loop()
-> for number in num_list:
(Pdb) step
> /Users/sammy/looping.py(7)nested_loop()
-> print(number)
(Pdb) step
500
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb) step
> /Users/sammy/looping.py(9)nested_loop()
-> print(letter)
(Pdb) step
x
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb) step
> /Users/sammy/looping.py(9)nested_loop()
-> print(letter)
(Pdb) step
y
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb)

La commande next, en revanche, exécutera la fonction entière sans montrer le processus étape par étape. Quittons la session actuelle avec la commande exit et recommençons le débogueur :

  1. python -m pdb looping.py

Maintenant, nous pouvons travailler avec la commande next:

(Pdb) next
> /Users/sammy/looping.py(5)<module>()
-> def nested_loop():
(Pdb) next
> /Users/sammy/looping.py(11)<module>()
-> if __name__ == '__main__':
(Pdb) next
> /Users/sammy/looping.py(12)<module>()
-> nested_loop()
(Pdb) next
500
x
y
z
600
x
y
z
700
x
y
z
--Return--
> /Users/sammy/looping.py(12)<module>()->None
-> nested_loop()
(Pdb)  

En parcourant votre code, vous voudrez peut-être examiner la valeur passée à une variable, ce que vous pouvez faire avec la commande pp, qui affichera joliment la valeur de l'expression à l'aide du module pprint :

(Pdb) pp num_list
[500, 600, 700]
(Pdb) 

La plupart des commandes dans pdb ont des alias plus courts. Pour step, cette forme courte est s, et pour next, c'est n. La commande help affichera les alias disponibles. Vous pouvez également rappeler la dernière commande que vous avez exécutée en appuyant sur la touche ENTER à l'invite.

Points d'arrêt

Vous travaillerez généralement avec des programmes plus importants que l'exemple ci-dessus, donc vous voudrez probablement examiner des fonctions ou des lignes particulières plutôt que de parcourir un programme entier. En utilisant la commande break pour définir des points d'arrêt, vous exécuterez le programme jusqu'au point d'arrêt spécifié.

Lorsque vous insérez un point d'arrêt, le débogueur lui attribue un numéro. Les numéros attribués aux points d'arrêt sont des entiers consécutifs commençant par le numéro 1, que vous pouvez consulter lorsque vous travaillez avec des points d'arrêt.

Des points d'arrêt peuvent être placés à certains numéros de ligne en suivant la syntaxe de <nom_du_programme>:<numéro_de_ligne> comme montré dans ce qui suit :

(Pdb) break looping.py:5
Breakpoint 1 at /Users/sammy/looping.py:5
(Pdb)

Tapez clear puis y pour supprimer tous les points d'arrêt actuels. Vous pouvez ensuite placer un point d'arrêt là où une fonction est définie :

(Pdb) break looping.nested_loop
Breakpoint 1 at /Users/sammy/looping.py:5
(Pdb) 

Pour supprimer les points d'arrêt actuels, tapez clear puis y. Vous pouvez également définir une condition :

(Pdb) break looping.py:7, number > 500
Breakpoint 1 at /Users/sammy/looping.py:7
(Pdb)     

Maintenant, si nous lançons la commande continue, le programme s'interrompra lorsque le nombre x sera évalué comme étant supérieur à 500 (c'est-à-dire lorsqu'il est fixé à 600 lors de la deuxième itération de la boucle externe) :

(Pdb) continue
500
x
y
z
> /Users/sammy/looping.py(7)nested_loop()
-> print(number)
(Pdb) 

Pour voir une liste des points d'arrêt qui sont actuellement définis pour s'exécuter, utilisez la commande break sans aucun argument. Vous recevrez des informations sur les particularités des points d'arrêt que vous avez définis :

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /Users/sammy/looping.py:7
	stop only if number > 500
	breakpoint already hit 2 times
(Pdb) 

Nous pouvons également désactiver un point d'arrêt avec la commande disable et le numéro du point d'arrêt. Dans cette session, nous ajoutons un autre point d'arrêt puis désactivons le premier :

(Pdb) break looping.py:11
Breakpoint 2 at /Users/sammy/looping.py:11
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/looping.py:7
(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep no    at /Users/sammy/looping.py:7
	stop only if number > 500
	breakpoint already hit 2 times
2   breakpoint   keep yes   at /Users/sammy/looping.py:11
(Pdb) 

Pour activer un point d'arrêt, utilisez la commande enable, et pour supprimer complètement un point d'arrêt, utilisez la commande clear:

(Pdb) enable 1
Enabled breakpoint 1 at /Users/sammy/looping.py:7
(Pdb) clear 2
Deleted breakpoint 2 at /Users/sammy/looping.py:11
(Pdb) 

Les points d'arrêt dans pdb vous offrent un grand contrôle. Certaines fonctionnalités supplémentaires incluent l'ignorance des points d'arrêt pendant l'itération actuelle du programme avec la commande ignore (comme dans ignore 1), le déclenchement d'actions à un point d'arrêt avec la commande commands (comme dans command 1), et la création de points d'arrêt temporaires qui sont automatiquement supprimés la première fois que l'exécution du programme atteint le point avec la commande tbreak (pour un point d'arrêt temporaire à la ligne 3, par exemple, vous pourriez taper tbreak 3).

Intégrer pdb dans des programmes

Vous pouvez déclencher une session de débogage en important le module pdb et en ajoutant la fonction pdb.set_trace() au-dessus de la ligne où vous souhaitez que la session commence.

Dans notre programme d'exemple ci-dessus, nous ajouterons l'instruction import et la fonction où nous aimerions entrer dans le débogueur. Pour notre exemple, ajoutons-la avant la boucle imbriquée.

Import pdb module
import pdb

num_list = [500, 600, 700]
alpha_list = ['x', 'y', 'z']


def nested_loop():
    for number in num_list:
        print(number)

        # Trigger debugger at this line
        pdb.set_trace()
        for letter in alpha_list:
            print(letter)

if __name__ == '__main__':
    nested_loop()

En ajoutant le débogueur dans votre code, vous n'avez pas besoin de lancer votre programme de manière spéciale ni de vous rappeler de définir des points d'arrêt.

L'importation du module pdb et l'exécution de la fonction pdb.set_trace() vous permettent de commencer votre programme comme d'habitude et d'exécuter le débogueur pendant son exécution.

Modification du flux d'exécution du programme

Le débogueur Python vous permet de changer le flux de votre programme à l'exécution avec la commande jump. Cela vous permet de sauter en avant pour empêcher l'exécution de certains codes, ou de revenir en arrière pour exécuter à nouveau le code.

Nous allons travailler avec un petit programme qui crée une liste des lettres contenues dans la chaîne sammy="sammy":

def print_sammy():
    sammy_list = []
    sammy = "sammy"
    for letter in sammy:
        sammy_list.append(letter)
        print(sammy_list)

if __name__ == "__main__":
    print_sammy()

Si nous exécutons le programme comme d'habitude avec la commande python letter_list.py, nous recevrons la sortie suivante :

['s']
['s', 'a']
['s', 'a', 'm']
['s', 'a', 'm', 'm']
['s', 'a', 'm', 'm', 'y']

Avec le débogueur Python, montrons comment nous pouvons modifier l'exécution en sautant en avant après le premier cycle. Lorsque nous faisons cela, nous noterons qu'il y a une disruption de la boucle for :

  1. python -m pdb letter_list.py
> /Users/sammy/letter_list.py(1)<module>()
-> def print_sammy():
(Pdb) list
  1  ->	def print_sammy():
  2  	    sammy_list = []
  3  	    sammy = "sammy"
  4  	    for letter in sammy:
  5  	        sammy_list.append(letter)
  6  	        print(sammy_list)
  7  	
  8  	if __name__ == "__main__":
  9  	    print_sammy()
 10  	
 11  	
(Pdb) break 5
Breakpoint 1 at /Users/sammy/letter_list.py:5
(Pdb) continue
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) pp letter
's'
(Pdb) continue
['s']
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) jump 6
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
'a'
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/letter_list.py:5
(Pdb) continue
['s']
['s', 'm']
['s', 'm', 'm']
['s', 'm', 'm', 'y']

La session de débogage ci-dessus met un point d'arrêt à la ligne 5 pour empêcher le code de continuer, puis continue à travers le code (tout en imprimant joliment certaines valeurs de letter pour montrer ce qui se passe). Ensuite, nous utilisons la commande jump pour sauter à la ligne 6. À ce stade, la variable letter est égale à la chaîne 'a', mais nous sautons le code qui l'ajoute à la liste sammy_list. Nous désactivons ensuite le point d'arrêt pour poursuivre l'exécution normalement avec la commande continue, donc 'a' n'est jamais ajouté à sammy_list.

Ensuite, nous pouvons quitter cette première session et redémarrer le débogueur pour revenir dans le programme afin de réexécuter une instruction qui a déjà été exécutée. Cette fois, nous allons exécuter à nouveau la première itération de la boucle for dans le débogueur :

> /Users/sammy/letter_list.py(1)<module>()
-> def print_sammy():
(Pdb) list
  1  ->	def print_sammy():
  2  	    sammy_list = []
  3  	    sammy = "sammy"
  4  	    for letter in sammy:
  5  	        sammy_list.append(letter)
  6  	        print(sammy_list)
  7  	
  8  	if __name__ == "__main__":
  9  	    print_sammy()
 10  	
 11  	
(Pdb) break 6
Breakpoint 1 at /Users/sammy/letter_list.py:6
(Pdb) continue
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
's'
(Pdb) jump 5
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) continue
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
's'
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/letter_list.py:6
(Pdb) continue
['s', 's']
['s', 's', 'a']
['s', 's', 'a', 'm']
['s', 's', 'a', 'm', 'm']
['s', 's', 'a', 'm', 'm', 'y']

Dans la session de débogage ci-dessus, nous avons ajouté un point d'arrêt à la ligne 6, puis nous avons sauté en arrière à la ligne 5 après avoir continué. Nous avons affiché le tout de manière lisible en cours de route pour montrer que la chaîne 's' était ajoutée à la liste sammy_list deux fois. Nous avons ensuite désactivé le point d'arrêt à la ligne 6 et continué à exécuter le programme. La sortie montre deux valeurs de 's' ajoutées à sammy_list.

Certains sauts sont empêchés par le débogueur, en particulier lors des sauts dans et hors de certaines instructions de contrôle de flux qui ne sont pas définies. Par exemple, vous ne pouvez pas sauter dans des fonctions avant que les arguments ne soient définis, et vous ne pouvez pas sauter au milieu d'une instruction try:except. Vous ne pouvez pas non plus sortir d'un bloc finally.

L'instruction jump avec le débogueur Python vous permet de modifier le flux d'exécution tout en déboguant un programme afin de voir si le contrôle de flux peut être modifié à d'autres fins ou pour mieux comprendre quels problèmes apparaissent dans votre code.

Table des commandes pdb courantes

Voici un tableau des commandes pdb utiles ainsi que leurs abréviations à garder à l'esprit lors de l'utilisation du débogueur Python.

CommandeAbréviationCe que ça fait
args
a
Imprimez la liste des arguments de la fonction actuelle
break
b
Crée un point d'arrêt (nécessite des paramètres) dans l'exécution du programme.
continue
c or cont
Poursuit l'exécution du programme
help
h
Fournit une liste de commandes ou d'aide pour une commande spécifiée
jump
j
Définissez la ligne suivante à exécuter
list
l
Imprimez le code source autour de la ligne actuelle
next
n
Continuez l'exécution jusqu'à ce que la ligne suivante de la fonction actuelle soit atteinte ou renvoie.
step
s
Exécutez la ligne actuelle, en vous arrêtant à la première occasion possible.
pp
pp
Affiche joliment la valeur de l'expression
quit or exit
q
Interrompt le programme
return
r
Continuez l'exécution jusqu'à ce que la fonction actuelle retourne

Vous pouvez en savoir plus sur les commandes et le travail avec le débogueur dans la documentation du débogueur Python.

Conclusion

Le débogage est une étape importante de tout projet de développement logiciel. Le débogueur Python pdb met en œuvre un environnement de débogage interactif que vous pouvez utiliser avec n'importe lequel de vos programmes écrits en Python.

Avec des fonctionnalités qui vous permettent de mettre votre programme en pause, de voir quelles sont les valeurs de vos variables, et de parcourir l'exécution du programme de manière discrète étape par étape, vous pouvez mieux comprendre ce que fait votre programme et trouver des bugs qui existent dans la logique ou dépanner des problèmes connus.