Débogage Python avec Pdb

Débogage Python avec Pdb

Le débogage d'applications peut parfois être une activité indésirable. Vous êtes occupé à travailler dans le cadre d'un resserrement du temps et vous voulez juste que cela fonctionne. Cependant, à d'autres moments, vous pourriez apprendre une nouvelle fonctionnalité de langue ou expérimenter une nouvelle approche et vouloir comprendre plus profondément comment quelque chose fonctionne.

Quelle que soit la situation, le débogage du code est une nécessité, c'est donc une bonne idée d'être à l'aise de travailler dans un débogueur. Dans ce didacticiel, je vais vous montrer les bases de l'utilisation de pdb, le débogueur de code source interactif de Python.

Je vais vous expliquer quelques utilisations courantes de pdb. Vous voudrez peut-être ajouter ce didacticiel à vos favoris pour une référence rapide plus tard lorsque vous en aurez vraiment besoin. pdb et d'autres débogueurs sont des outils indispensables. Lorsque vous avez besoin d'un débogueur, il n'y a pas de substitut. Vous en avez vraiment besoin.

À la fin de ce didacticiel, vous saurez comment utiliser le débogueur pour voir l'état d'une variable dans votre application. Vous pourrez également arrêter et reprendre le flux d’exécution de votre application à tout moment, afin de voir exactement comment chaque ligne de code affecte son état interne.

Ceci est idéal pour localiser les bogues difficiles à trouver et vous permet de corriger le code défectueux plus rapidement et de manière fiable. Parfois, parcourir le code dans pdb et voir comment les valeurs changent peut être une véritable révélation et conduire à des moments «aha», avec parfois la «paume du visage».

pdb fait partie de la bibliothèque standard de Python, il est donc toujours là et disponible pour utilisation. Cela peut vous sauver la vie si vous devez déboguer du code dans un environnement où vous n'avez pas accès au débogueur de l'interface graphique que vous connaissez.

L'exemple de code de ce didacticiel utilise Python 3.6. Vous pouvez trouver le code source de ces exemples surGitHub.

À la fin de ce didacticiel, vous trouverez une référence rapide pourEssential pdb Commands.

Il existe également une référence de commande pdb imprimable que vous pouvez utiliser comme aide-mémoire lors du débogage:

Free Bonus:Click here to get a printable "pdb Command Reference" (PDF) que vous pouvez garder sur votre bureau et auquel vous pouvez vous référer pendant le débogage.

Mise en route: impression de la valeur d'une variable

Dans ce premier exemple, nous allons voir comment utiliser pdb dans sa forme la plus simple: vérifier la valeur d'une variable.

Insérez le code suivant à l'emplacement où vous souhaitez pénétrer dans le débogueur:

import pdb; pdb.set_trace()

Lorsque la ligne ci-dessus est exécutée, Python s'arrête et attend que vous lui disiez quoi faire ensuite. Vous verrez une invite(Pdb). Cela signifie que vous êtes maintenant en pause dans le débogueur interactif et pouvez entrer une commande.

À partir de Python 3.7,there’s another way to enter the debugger. PEP 553 décrit la fonction intégréebreakpoint(), qui rend la saisie du débogueur facile et cohérente:

breakpoint()

Par défaut,breakpoint() importerapdb et appellerapdb.set_trace(), comme indiqué ci-dessus. Cependant, l'utilisation debreakpoint() est plus flexible et vous permet de contrôler le comportement de débogage via son API et l'utilisation de la variable d'environnementPYTHONBREAKPOINT. Par exemple, la configuration dePYTHONBREAKPOINT=0 dans votre environnement désactivera complètementbreakpoint(), désactivant ainsi le débogage. Si vous utilisez Python 3.7 ou version ultérieure, je vous encourage à utiliserbreakpoint() au lieu depdb.set_trace().

Vous pouvez également entrer dans le débogueur, sans modifier la source et en utilisantpdb.set_trace() oubreakpoint(), en exécutant Python directement à partir de la ligne de commande et en passant l'option-m pdb. Si votre application accepte les arguments de ligne de commande, passez-les comme vous le feriez normalement après le nom de fichier. Par exemple:

$ python3 -m pdb app.py arg1 arg2

Il existe de nombreuses commandes pdb disponibles. À la fin de ce tutoriel, il y a une liste deEssential pdb Commands. Pour l’instant, utilisons la commandep pour afficher la valeur d’une variable. Entrezp variable_name à l'invite(Pdb) pour imprimer sa valeur.

Regardons l'exemple. Voici la source deexample1.py:

#!/usr/bin/env python3

filename = __file__
import pdb; pdb.set_trace()
print(f'path = {filename}')

Si vous l'exécutez à partir de votre shell, vous devriez obtenir la sortie suivante:

$ ./example1.py
> /code/example1.py(5)()
-> print(f'path = {filename}')
(Pdb)

Si vous ne parvenez pas à exécuter les exemples ou votre propre code à partir de la ligne de commande, lisezHow Do I Make My Own Command-Line Commands Using Python? Si vous êtes sous Windows, vérifiez lesPython Windows FAQ.

Entrez maintenantp filename. Tu devrais voir:

(Pdb) p filename
'./example1.py'
(Pdb)

Étant donné que vous êtes dans un shell et que vous utilisez une CLI (interface de ligne de commande), faites attention aux caractères et à la mise en forme. Ils vous donneront le contexte dont vous avez besoin:

  • > commence la 1ère ligne et vous indique dans quel fichier source vous vous trouvez. Après le nom de fichier, il y a le numéro de ligne actuel entre parenthèses. Vient ensuite le nom de la fonction. Dans cet exemple, puisque nous ne sommes pas en pause dans une fonction et au niveau du module, nous voyons<module>().

  • -> commence la 2ème ligne et est la ligne source actuelle où Python est mis en pause. Cette ligne n'a pas encore été exécutée. Dans cet exemple, il s'agit de la ligne5 enexample1.py, de la ligne> ci-dessus.

  • (Pdb) est l'invite de pdb. Il attend une commande.

Utilisez la commandeq pour quitter le débogage et quitter.

Impression d'expressions

Lorsque vous utilisez la commande d'impressionp, vous transmettez une expression à évaluer par Python. Si vous passez un nom de variable, pdb affiche sa valeur actuelle. Cependant, vous pouvez faire beaucoup plus pour enquêter sur l'état de votre application en cours d'exécution.

Dans cet exemple, la fonctionget_path() est appelée. Pour inspecter ce qui se passe dans cette fonction, j'ai inséré un appel àpdb.set_trace() pour suspendre l'exécution juste avant son retour:

#!/usr/bin/env python3

import os


def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    import pdb; pdb.set_trace()
    return head


filename = __file__
print(f'path = {get_path(filename)}')

Si vous l'exécutez à partir de votre shell, vous devriez obtenir la sortie:

$ ./example2.py
> /code/example2.py(10)get_path()
-> return head
(Pdb)

Où sommes-nous?

  • >: Nous sommes dans le fichier sourceexample2.py à la ligne10 dans la fonctionget_path(). C'est le cadre de référence que la commandep utilisera pour résoudre les noms de variables, c.-à-d. la portée ou le contexte actuel.

  • ->: l'exécution s'est interrompue àreturn head. Cette ligne n'a pas encore été exécutée. Il s'agit de la ligne10 enexample2.py dans la fonctionget_path(), à partir de la ligne> ci-dessus.

Imprimons quelques expressions pour examiner l'état actuel de l'application. J'utilise la commandell (longue liste) au départ pour lister la source de la fonction:

(Pdb) ll
  6     def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         import pdb; pdb.set_trace()
 10  ->     return head
(Pdb) p filename
'./example2.py'
(Pdb) p head, tail
('.', 'example2.py')
(Pdb) p 'filename: ' + filename
'filename: ./example2.py'
(Pdb) p get_path

(Pdb) p getattr(get_path, '__doc__')
"Return file's path or empty string if no path."
(Pdb) p [os.path.split(p)[1] for p in os.path.sys.path]
['pdb-basics', 'python36.zip', 'python3.6', 'lib-dynload', 'site-packages']
(Pdb)

Vous pouvez transmettre toute expression Python valide àp pour évaluation.

Cela est particulièrement utile lorsque vous déboguez et souhaitez tester une implémentation alternative directement dans l'application au moment de l'exécution.

Vous pouvez également utiliser la commandepp (pretty-print) pour imprimer de jolies expressions. Cela est utile si vous souhaitez imprimer une variable ou une expression avec une grande quantité de sortie, par ex. listes et dictionnaires. La jolie impression conserve les objets sur une seule ligne si elle le peut ou les divise en plusieurs lignes s'ils ne tiennent pas dans la largeur autorisée.

Passer par le code

Il existe deux commandes que vous pouvez utiliser pour parcourir le code lors du débogage:

Commander La description

n (suivant)

Continuez l'exécution jusqu'à ce que la ligne suivante de la fonction actuelle soit atteinte ou qu'elle retourne.

s (étape)

Exécutez la ligne en cours et arrêtez-vous à la première occasion possible (soit dans une fonction appelée, soit dans la fonction en cours).

Il existe une troisième commande nomméeunt (jusqu'à). Il est lié àn (suivant). Nous l'examinerons plus tard dans ce tutoriel dans la sectionContinuing Execution.

La différence entren (suivant) ets (étape) est l'endroit où pdb s'arrête.

Utilisezn (next) pour continuer l'exécution jusqu'à la ligne suivante et rester dans la fonction actuelle, c.-à-d. ne s'arrête pas dans une fonction étrangère si l'on est appelé. Pensez ensuite à «rester local» ou à «enjamber».

Utilisezs (step) pour exécuter la ligne courante et s'arrêter dans une fonction étrangère si elle est appelée. Considérez l'étape comme «entrer». Si l'exécution est arrêtée dans une autre fonction,s affichera--Call--.

Les deuxn ets arrêteront l'exécution lorsque la fin de la fonction actuelle est atteinte et afficheront--Return-- avec la valeur de retour à la fin de la ligne suivante après->.

Regardons un exemple utilisant les deux commandes. Voici la source deexample3.py:

#!/usr/bin/env python3

import os


def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    return head


filename = __file__
import pdb; pdb.set_trace()
filename_path = get_path(filename)
print(f'path = {filename_path}')

Si vous exécutez ceci à partir de votre shell et entrezn, vous devriez obtenir la sortie:

$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) n
> /code/example3.py(15)()
-> print(f'path = {filename_path}')
(Pdb)

Avecn (suivant), nous nous sommes arrêtés sur la ligne15, la ligne suivante. Nous sommes «restés locaux» dans<module>() et avons «dépassé» l'appel àget_path(). La fonction est<module>() car nous sommes actuellement au niveau du module et pas en pause dans une autre fonction.

Essayonss:

$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb)

Avecs (step), nous nous sommes arrêtés sur la ligne6 dans la fonctionget_path() puisqu'elle a été appelée sur la ligne14. Notez la ligne--Call-- après la commandes.

Idéalement, pdb se souvient de votre dernière commande. Si vous parcourez beaucoup de code, vous pouvez simplement appuyer sur[.kbd .key-enter]#Enter # pour répéter la dernière commande.

Voici un exemple d'utilisation des etn pour parcourir le code. J'entre d'abords parce que je veux «entrer» dans la fonctionget_path() et m'arrêter. Ensuite, j'entren une fois pour «rester local» ou «enjamber» tout autre appel de fonction et appuyez simplement sur[.kbd .key-enter]#Enter # pour répéter la commanden jusqu'à ce que j'arrive à la dernière ligne source.

$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) n
> /code/example3.py(8)get_path()
-> head, tail = os.path.split(filename)
(Pdb)
> /code/example3.py(9)get_path()
-> return head
(Pdb)
--Return--
> /code/example3.py(9)get_path()->'.'
-> return head
(Pdb)
> /code/example3.py(15)()
-> print(f'path = {filename_path}')
(Pdb)
path = .
--Return--
> /code/example3.py(15)()->None
-> print(f'path = {filename_path}')
(Pdb)

Notez les lignes--Call-- et--Return--. Ceci est pdb vous permettant de savoir pourquoi l'exécution a été arrêtée. n (suivant) ets (étape) s'arrêteront avant le retour d'une fonction. C’est pourquoi vous voyez les lignes--Return-- ci-dessus.

Notez également->'.' à la fin de la ligne après le premier--Return-- ci-dessus:

--Return--
> /code/example3.py(9)get_path()->'.'
-> return head
(Pdb)

Lorsque pdb s'arrête à la fin d'une fonction avant son retour, il imprime également la valeur de retour pour vous. Dans cet exemple, il s’agit de'.'.

Liste du code source

N'oubliez pas la commandell (longlist: listez tout le code source de la fonction ou du cadre en cours). C'est très utile lorsque vous parcourez un code inconnu ou que vous souhaitez simplement voir la fonction entière pour le contexte.

Voici un exemple:

$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) ll
  6  -> def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         return head
(Pdb)

Pour voir un extrait de code plus court, utilisez la commandel (liste). Sans arguments, il imprimera 11 lignes autour de la ligne actuelle ou continuera la liste précédente. Passez l'argument. pour toujours lister 11 lignes autour de la ligne courante:l .

$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) l
  9         return head
 10
 11
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb) l
[EOF]
(Pdb) l .
  9         return head
 10
 11
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb)

Utilisation de points d'arrêt

Les points d'arrêt sont très pratiques et peuvent vous faire gagner beaucoup de temps. Au lieu de parcourir des dizaines de lignes qui ne vous intéressent pas, créez simplement un point d'arrêt où vous souhaitez enquêter. Facultativement, vous pouvez également indiquer à pdb de ne s'arrêter que lorsqu'une certaine condition est remplie.

Utilisez la commandeb (break) pour définir un point d'arrêt. Vous pouvez spécifier un numéro de ligne ou un nom de fonction où l'exécution est arrêtée.

La syntaxe de break est:

b(reak) [ ([filename:]lineno | function) [, condition] ]

Sifilename: n'est pas spécifié avant le numéro de lignelineno, le fichier source actuel est utilisé.

Notez le deuxième argument facultatif deb:condition. C'est très puissant. Imaginez une situation où vous ne vouliez rompre que si une certaine condition existait. Si vous passez une expression Python comme 2e argument, pdb se cassera lorsque l'expression sera évaluée à true. Nous allons le faire dans un exemple ci-dessous.

Dans cet exemple, il y a un module utilitaireutil.py. Définissons un point d'arrêt pour arrêter l'exécution dans la fonctionget_path().

Voici la source du script principalexample4.py:

#!/usr/bin/env python3

import util

filename = __file__
import pdb; pdb.set_trace()
filename_path = util.get_path(filename)
print(f'path = {filename_path}')

Voici la source du module utilitaireutil.py:

def get_path(filename):
    """Return file's path or empty string if no path."""
    import os
    head, tail = os.path.split(filename)
    return head

Commençons par définir un point d'arrêt à l'aide du nom de fichier source et du numéro de ligne:

$ ./example4.py
> /code/example4.py(7)()
-> filename_path = util.get_path(filename)
(Pdb) b util:5
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p filename, head, tail
('./example4.py', '.', 'example4.py')
(Pdb)

La commandec (continue) continue son exécution jusqu'à ce qu'un point d'arrêt soit trouvé.

Ensuite, définissons un point d'arrêt à l'aide du nom de la fonction:

$ ./example4.py
> /code/example4.py(7)()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) p filename
'./example4.py'
(Pdb)

Entrezb sans argument pour voir une liste de tous les points d'arrêt:

(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /code/util.py:1
(Pdb)

Vous pouvez désactiver et réactiver les points d'arrêt à l'aide des commandesdisable bpnumber etenable bpnumber. bpnumber est le numéro de point d'arrêt de la 1ère colonne de la liste des points d'arrêtNum. Notez le changement de valeur de la colonneEnb:

(Pdb) disable 1
Disabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep no    at /code/util.py:1
(Pdb) enable 1
Enabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /code/util.py:1
(Pdb)

Pour supprimer un point d'arrêt, utilisez la commandecl (effacer):

cl(ear) filename:lineno
cl(ear) [bpnumber [bpnumber...]]

Utilisons maintenant une expression Python pour définir un point d'arrêt. Imaginez une situation où vous ne vouliez rompre que si votre fonction en difficulté recevait une certaine entrée.

Dans cet exemple de scénario, la fonctionget_path() échoue lorsqu'elle reçoit un chemin relatif, c'est-à-dire le chemin du fichier ne commence pas par/. Je vais créer une expression qui donne la valeur true dans ce cas et la transmettre àb comme deuxième argument:

$ ./example4.py
> /code/example4.py(7)()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path, not filename.startswith('/')
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) a
filename = './example4.py'
(Pdb)

Après avoir créé le point d'arrêt ci-dessus et entréc pour continuer l'exécution, pdb s'arrête lorsque l'expression prend la valeur true. La commandea (args) imprime la liste des arguments de la fonction courante.

Dans l'exemple ci-dessus, lorsque vous définissez le point d'arrêt avec un nom de fonction plutôt qu'un numéro de ligne, notez que l'expression ne doit utiliser que des arguments de fonction ou des variables globales disponibles au moment où la fonction est entrée. Sinon, le point d'arrêt arrêtera l'exécution dans la fonction quelle que soit la valeur de l'expression.

Si vous avez besoin de rompre en utilisant une expression avec un nom de variable situé à l'intérieur d'une fonction, c'est-à-dire un nom de variable ne figurant pas dans la liste des arguments de la fonction, spécifiez le numéro de ligne:

$ ./example4.py
> /code/example4.py(7)()
-> filename_path = util.get_path(filename)
(Pdb) b util:5, not head.startswith('/')
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p head
'.'
(Pdb) a
filename = './example4.py'
(Pdb)

Vous pouvez également définir un point d'arrêt temporaire à l'aide de la commandetbreak. Il est supprimé automatiquement lors de son premier coup. Il utilise les mêmes arguments queb.

Exécution continue

Jusqu'à présent, nous avons cherché à parcourir le code avecn (suivant) ets (étape) et à utiliser des points d'arrêt avecb (break) etc (continue) .

Il existe également une commande associée:unt (jusqu'à).

Utilisezunt pour continuer l'exécution commec, mais arrêtez-vous à la ligne suivante supérieure à la ligne actuelle. Parfois,unt est plus pratique et plus rapide à utiliser et correspond exactement à ce que vous voulez. Je vais le démontrer avec un exemple ci-dessous.

Examinons d'abord la syntaxe et la description deunt:

Commander Syntaxe La description

unt

unt (il) [lineno]

Sanslineno, continuez l'exécution jusqu'à ce que la ligne avec un nombre supérieur à l'actuel soit atteinte. Aveclineno, continuez l'exécution jusqu'à ce qu'une ligne avec un nombre supérieur ou égal à celui-ci soit atteinte. Dans les deux cas, arrêtez également lorsque l'image actuelle revient.

Selon que vous transmettez ou non l'argument de numéro de lignelineno,unt peut se comporter de deux manières:

  • Sanslineno, continuez l'exécution jusqu'à ce que la ligne avec un nombre supérieur à l'actuel soit atteinte. Ceci est similaire àn (suivant). C’est une autre façon d’exécuter et de «passer par-dessus» le code. La différence entren etunt est queunt s'arrête uniquement lorsqu'une ligne avec un nombre supérieur à celui actuel est atteinte. n s'arrêtera à la prochaine ligne exécutée logiquement.

  • Aveclineno, continuez l'exécution jusqu'à ce qu'une ligne avec un nombre supérieur ou égal à celui-ci soit atteinte. C'est commec (continue) avec un argument de numéro de ligne.

Dans les deux cas,unt s'arrête lorsque la trame (fonction) actuelle revient, tout commen (suivant) ets (étape).

Le comportement principal à noter avecunt est qu'il s'arrêtera lorsqu'un numéro de lignegreater or equal à la ligne actuelle ou spécifiée est atteint.

Utilisezunt lorsque vous souhaitez poursuivre l'exécution et vous arrêter plus bas dans le fichier source actuel. Vous pouvez le traiter comme un hybride den (suivant) etb (rupture), selon que vous passez un argument de numéro de ligne ou non.

Dans l'exemple ci-dessous, il existe une fonction avec une boucle. Ici, vous souhaitez continuer l'exécution du code et vous arrêter après la boucle, sans passer par chaque itération de la boucle ou définir un point d'arrêt:

Voici l'exemple de source pourexample4unt.py:

#!/usr/bin/env python3

import os


def get_path(fname):
    """Return file's path or empty string if no path."""
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    for char in tail:
        pass  # Check filename char
    return head


filename = __file__
filename_path = get_path(filename)
print(f'path = {filename_path}')

Et la sortie de la console utilisantunt:

$ ./example4unt.py
> /code/example4unt.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) unt
> /code/example4unt.py(10)get_path()
-> for char in tail:
(Pdb)
> /code/example4unt.py(11)get_path()
-> pass  # Check filename char
(Pdb)
> /code/example4unt.py(12)get_path()
-> return head
(Pdb) p char, tail
('y', 'example4unt.py')

La commandell a été utilisée en premier pour imprimer la source de la fonction, suivie deunt. pdb se souvient de la dernière commande entrée, donc j'ai juste appuyé sur[.kbd .key-enter]#Enter # pour répéter la commandeunt. Cette exécution continue à travers le code jusqu'à ce qu'une ligne source supérieure à la ligne actuelle soit atteinte.

Notez dans la sortie de la console ci-dessus que pdb ne s'est arrêté qu'une seule fois sur les lignes10 et11. Puisqueunt a été utilisé, l'exécution n'a été arrêtée que dans la 1ère itération de la boucle. Cependant, chaque itération de la boucle a été exécutée. Cela peut être vérifié dans la dernière ligne de sortie. La valeur de la variablechar'y' est égale au dernier caractère de la valeurtail'example4unt.py'.

Affichage des expressions

Similaire à l'impression d'expressions avecp etpp, vous pouvez utiliser la commandedisplay [expression] pour indiquer à pdb d'afficher automatiquement la valeur d'une expression, si elle a changé, lorsque l'exécution s'arrête. Utilisez la commandeundisplay [expression] pour effacer une expression d'affichage.

Voici la syntaxe et la description des deux commandes:

Commander Syntaxe La description

display

afficher [expression]

Affiche la valeur deexpression si elle a changé, chaque fois que l'exécution s'arrête dans la trame courante. Sansexpression, répertoriez toutes les expressions d'affichage pour l'image actuelle.

undisplay

undisplay [expression]

N'affiche plusexpression dans l'image courante. Sansexpression, effacez toutes les expressions d'affichage de l'image actuelle.

Voici un exemple,example4display.py, démontrant son utilisation avec une boucle:

$ ./example4display.py
> /code/example4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at /code/example4display.py:11
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb)
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'a'  [old: 'x']
(Pdb)
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'm'  [old: 'a']

Dans la sortie ci-dessus, pdb affichait automatiquement la valeur de la variablechar car chaque fois que le point d'arrêt était atteint, sa valeur avait changé. Parfois, c'est utile et exactement ce que vous voulez, mais il existe une autre façon d'utiliserdisplay.

Vous pouvez entrerdisplay plusieurs fois pour créer une liste de surveillance d'expressions. Cela peut être plus facile à utiliser quep. Après avoir ajouté toutes les expressions qui vous intéressent, entrez simplementdisplay pour voir les valeurs actuelles:

$ ./example4display.py
> /code/example4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at /code/example4display.py:11
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) display fname
display fname: './example4display.py'
(Pdb) display head
display head: '.'
(Pdb) display tail
display tail: 'example4display.py'
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb) display
Currently displaying:
char: 'x'
fname: './example4display.py'
head: '.'
tail: 'example4display.py'

ID d'appelant Python

Dans cette dernière section, nous allons construire sur ce que nous avons appris jusqu'à présent et terminer avec une belle récompense. J'utilise le nom «identification de l'appelant» en référence à la fonction d'identification de l'appelant du système téléphonique. C’est exactement ce que cet exemple montre, sauf qu’il s’applique à Python.

Voici la source du script principalexample5.py:

#!/usr/bin/env python3

import fileutil


def get_file_info(full_fname):
    file_path = fileutil.get_path(full_fname)
    return file_path


filename = __file__
filename_path = get_file_info(filename)
print(f'path = {filename_path}')

Voici le module utilitairefileutil.py:

def get_path(fname):
    """Return file's path or empty string if no path."""
    import os
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    return head

Dans ce scénario, imaginez qu'il existe une grande base de code avec une fonction dans un module utilitaire,get_path(), qui est appelée avec une entrée non valide. Cependant, il est appelé depuis de nombreux endroits dans différents packages.

Comment trouvez-vous qui est l'appelant?

Utilisez la commandew (où) pour imprimer une trace de pile, avec la trame la plus récente en bas:

$ ./example5.py
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/example5.py(12)()
-> filename_path = get_file_info(filename)
  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb)

Ne vous inquiétez pas si cela semble déroutant ou si vous n'êtes pas sûr de ce qu'est une trace de pile ou un cadre. Je vais expliquer ces termes ci-dessous. Ce n'est pas aussi difficile que cela puisse paraître.

Puisque le cadre le plus récent est en bas, commencez par là et lisez de bas en haut. Regardez les lignes qui commencent par->, mais ignorez la 1ère instance car c'est là quepdb.set_trace() a été utilisé pour entrer pdb dans la fonctionget_path(). Dans cet exemple, la ligne source qui a appelé la fonctionget_path() est:

-> file_path = fileutil.get_path(full_fname)

La ligne au-dessus de chaque-> contient le nom de fichier, le numéro de ligne (entre parenthèses) et le nom de la fonction dans laquelle se trouve la ligne source. Donc, l'appelant est:

  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

Ce n'est pas surprenant dans ce petit exemple à des fins de démonstration, mais imaginez une grande application dans laquelle vous avez défini un point d'arrêt avec une condition pour identifier l'origine d'une mauvaise valeur d'entrée.

Nous savons maintenant comment trouver l'appelant.

Mais qu'en est-il de cette trace de pile et de ce truc de cadre?

Une trace de pile n'est qu'une liste de toutes les images que Python a créées pour garder une trace des appels de fonction. Un cadre est une structure de données créée par Python lorsqu'une fonction est appelée et supprimée lorsqu'elle revient. La pile est simplement une liste ordonnée de trames ou d'appels de fonction à tout moment. La pile (appel de fonction) augmente et se rétrécit tout au long de la vie d'une application à mesure que les fonctions sont appelées puis reviennent.

Lorsqu'elle est imprimée, cette liste ordonnée d'images, la pile, est appelée trace de pile. Vous pouvez le voir à tout moment en entrant la commandew, comme nous l'avons fait ci-dessus pour trouver l'appelant.

Voir cecall stack article on Wikipedia pour plus de détails.

Pour mieux comprendre et tirer le meilleur parti de pdb, examinons de plus près l’aide dew:

(Pdb) h w
w(here)
        Print a stack trace, with the most recent frame at the bottom.
        An arrow indicates the "current frame", which determines the
        context of most commands. 'bt' is an alias for this command.

Que signifie pdb par «image courante»?

Considérez la trame actuelle comme la fonction actuelle où pdb a arrêté l'exécution. En d'autres termes, le cadre actuel est l'endroit où votre application est actuellement en pause et est utilisé comme "cadre" de référence pour les commandes pdb commep (impression).

p et les autres commandes utiliseront l'image courante pour le contexte si nécessaire. Dans le cas dep, la trame courante sera utilisée pour rechercher et imprimer des références de variables.

Lorsque pdb imprime une trace de pile, une flèche> indique la trame actuelle.

Comment est-ce utile?

Vous pouvez utiliser les deux commandesu (haut) etd (bas) pour changer la trame actuelle. Combiné avecp, cela vous permet d'inspecter les variables et l'état de votre application à tout moment le long de la pile d'appels dans n'importe quel cadre.

Voici la syntaxe et la description des deux commandes:

Commander Syntaxe La description

u

u (p) [nombre]

Déplacez le niveau de l'image actuellecount (un par défaut) vers le haut dans la trace de pile (vers une image plus ancienne).

d

d (propre) [compte]

Déplacez le niveau de l'image actuellecount (un par défaut) vers le bas dans la trace de la pile (vers une image plus récente).

Regardons un exemple utilisant les commandesu etd. Dans ce scénario, nous voulons inspecter la variablefull_fname qui est locale à la fonctionget_file_info() dansexample5.py. Pour ce faire, nous devons changer la trame actuelle d'un niveau en utilisant la commandeu:

$ ./example5.py
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/example5.py(12)()
-> filename_path = get_file_info(filename)
  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u
> /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
(Pdb) p full_fname
'./example5.py'
(Pdb) d
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) p fname
'./example5.py'
(Pdb)

L'appel àpdb.set_trace() est enfileutil.py dans la fonctionget_path(), donc la trame courante y est initialement définie. Vous pouvez le voir dans la 1ère ligne de sortie ci-dessus:

> /code/fileutil.py(5)get_path()

Pour accéder et imprimer la variable localefull_fname dans la fonctionget_file_info() enexample5.py, la commandeu a été utilisée pour remonter d'un niveau:

(Pdb) u
> /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

Notez dans la sortie deu au-dessus que pdb a imprimé la flèche> au début de la 1ère ligne. Il s'agit de pdb vous permettant de savoir que le cadre a été modifié et que cet emplacement source est maintenant le cadre actuel. La variablefull_fname est désormais accessible. De plus, il est important de réaliser que la ligne source commençant par-> sur la deuxième ligne a été exécutée. Depuis que la trame a été déplacée vers le haut de la pile,fileutil.get_path() a été appelé. En utilisantu, nous avons remonté la pile (dans un sens, dans le temps) jusqu'à la fonctionexample5.get_file_info()fileutil.get_path() a été appelé.

Dans l'exemple suivant, après l'impression defull_fname, l'image actuelle a été déplacée vers son emplacement d'origine à l'aide ded et la variable localefname enget_path() a été imprimée.

Si nous le voulions, nous aurions pu déplacer plusieurs images à la fois en passant l'argumentcount àu oud. Par exemple, nous aurions pu passer au niveau du module dansexample5.py en entrantu 2:

$ ./example5.py
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u 2
> /code/example5.py(12)()
-> filename_path = get_file_info(filename)
(Pdb) p filename
'./example5.py'
(Pdb)

Il est facile d'oublier où vous êtes lorsque vous déboguez et pensez à beaucoup de choses différentes. N'oubliez pas que vous pouvez toujours utiliser la commande bien nomméew (where) pour voir où l'exécution est interrompue et quelle est la trame actuelle.

Commandes pdb essentielles

Une fois que vous aurez passé un peu de temps avec pdb, vous vous rendrez compte qu'un peu de connaissances va très loin. L'aide est toujours disponible avec la commandeh.

Entrez simplementh ouhelp <topic> pour obtenir une liste de toutes les commandes ou de l'aide pour une commande ou un sujet spécifique.

Pour une référence rapide, voici une liste des commandes essentielles:

Commander La description

p

Imprime la valeur d'une expression.

pp

Jolie-imprime la valeur d'une expression.

n

Continuez l'exécution jusqu'à ce que la ligne suivante de la fonction actuelle soit atteinte ou qu'elle retourne.

s

Exécutez la ligne en cours et arrêtez-vous à la première occasion possible (soit dans une fonction appelée, soit dans la fonction en cours).

c

Continuez l'exécution et ne vous arrêtez que lorsqu'un point d'arrêt est rencontré.

unt

Continuez l'exécution jusqu'à ce que la ligne avec un nombre supérieur à l'actuel soit atteinte. Avec un argument de numéro de ligne, continuez l'exécution jusqu'à ce qu'une ligne avec un nombre supérieur ou égal à celui-ci soit atteinte.

l

Liste le code source du fichier actuel. Sans arguments, listez 11 lignes autour de la ligne actuelle ou continuez la liste précédente.

ll

Répertoriez l'ensemble du code source de la fonction ou du cadre en cours

b

Sans arguments, listez toutes les pauses. Avec un argument de numéro de ligne, définissez un point d'arrêt à cette ligne dans le fichier actuel.

w

Imprimez une trace de pile, avec le cadre le plus récent en bas. Une flèche indique le cadre actuel, qui détermine le contexte de la plupart des commandes.

u

Déplacez le nombre d'images actuel (un par défaut) vers le haut dans la trace de pile (vers une image plus ancienne).

d

Déplacez le nombre d'images actuel (un par défaut) vers le bas dans la trace de la pile (vers une image plus récente).

h

Voir une liste des commandes disponibles.

h

Afficher l'aide pour une commande ou un sujet.

h pdb

Afficher la documentation complète de pdb.

q

Quittez le débogueur et quittez.

Débogage Python avec pdb: Conclusion

Dans ce tutoriel, nous avons couvert quelques utilisations de base et courantes de pdb:

  • impression d'expressions

  • pas à pas dans le code avecn (suivant) ets (étape)

  • en utilisant des points d'arrêt

  • exécution continue avecunt (jusqu'à)

  • affichage des expressions

  • trouver l'appelant d'une fonction

J'espère que cela vous a été utile. Si vous souhaitez en savoir plus, consultez:

Le code source utilisé dans les exemples se trouve sur lesGitHub repository associés. Assurez-vous de consulter notre référence de commande pdb imprimable, que vous pouvez utiliser comme aide-mémoire lors du débogage:

Free Bonus:Click here to get a printable "pdb Command Reference" (PDF) que vous pouvez garder sur votre bureau et auquel vous pouvez vous référer pendant le débogage.

De plus, si vous souhaitez essayer un débogueur Python basé sur l'interface graphique, lisez nosPython IDEs and Editors Guide pour voir quelles options vous conviennent le mieux. Pythoning heureux!