Débogage visuel
Objectifs
Afin de déboguer un programme, il est nécessaire de pouvoir introspecter l'état de celui-ci.
L'état du programme est par définition le contenu de sa mémoire, c'est à dire les instances ainsi que les liens entre celles-ci.
Jusqu'ici, pour pouvoir afficher des morceaux de cet état, nous avons simplement ajouté des appels à la fonction print dans le programme ou alors utilisé le débogueur Pdb.
Dans cet exercice, nous allons utiliser un petit outil maison de débogue : traceur.py disponible ici.
Comme son nom l'indique, cet outil est lui-même un module python.
ATTENTION ce petit module est fourni dans le but de vous aider à mettre au point vos programmes utilisant des listes chaînées : si son utilisation ne vous semble pas évidente ou que vous n'en voyez pas l'intérêt, ne perdez pas de temps là-dessus... la technique ancestrale consistant à dessiner vos listes à la main sur une feuille de papier reste toujours fonctionnelle !
ATTENTION pour que le traceur fonctionne il faut installer graphviz sur votre machine (que nous avons déjà utilisé dans le mini-projet "Mots suivants").
Tracer une fonction
Le traceur permet de tracer graphiquement tous les appels à une fonction.
Pour s'en servir, il suffit :
- d'ajouter
from traceur import traceau début de votre code ; - d'ajouter une ligne
@traceavant chaque déclaration de fonction que vous voulez tracer (techniquement, on parle d'annotation de fonction dans le jargon Python) ; - d'exécuter votre programme dans
terminology(terminal capable d'afficher des images).
Le traceur va alors afficher deux images pour chaque appel à chaque fonction tracée :
- une image avec les arguments de la fonctions (ce sont des variables, et donc des références) ainsi que toutes les instances accessibles depuis ces arguments ;
- une image avec l'instance retournée par la fonction ainsi que toutes les instances accessibles depuis cette instance.
Si vous ne disposez pas de terminology, il est toujours possible d'utiliser le traceur.
Pour cela, il suffit de donner la valeur False à l'argument visualize dans l'annotation de fonction : @trace(visualize=False).
Dans ce cas, le traceur va générer dans le répertoire courant les images correspondant aux appels des fonctions tracées.
Deux images sont générées telles que décrites ci-dessus.
Le nom des images générées est affiché sur la sortie standard.
Dans cet exercice, il est demandé dans premier temps de dessiner à la main sur papier toutes les instances accessibles depuis les arguments de l'appel de fonction ajoute_valeurs_cellules(cell41, cell42) dans le programme disponible ici et affiché ci-dessous :
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 | |
Ensuite, il est demandé d'installer le module traceur sur votre machine (exercice Installation d'un module) et d'exécuter test_traceur.py pour vérifier votre dessin ainsi que pour vous assurer d'avoir un module traceur opérationnel qui sera essentiel pour la suite des travaux pratiques. Si vous ne disposez pas de terminology il vous faudra modifier les annotations de fonctions dans test_traceur.py comme indiqué ci-dessus.
Correction
Cliquez ici pour révéler la correction.
Le résultat du traceur est le suivant :
Sur ce schéma, les couleurs ont la sémantique suivante :
- vert : les arguments donnés à la fonction avec leur nom. Ces derniers sont des variables locales à la fonction, donc des références vers des instances ;
- gris : les instances avec leur type et leur contenu ;
- rose : l'instance
None.
Nous retrouvons sur ce schéma les deux arguments cell1 et cell2 donnés à la fonction ajoute_valeurs_cellules.
cell1 est une référence vers une cellule dont :
- la
valeurest une référence vers l'entier41; - le
suivantest une référence vers une autre cellule, qui se trouve être la même que celle référencée parcell2.
cell2 est une référence vers une cellule dont :
- la
valeurest une référence vers l'entier42; - le
suivantest une référence versNone.
Tracer les variables et les instances qui nous intéressent
Il est également possible d'utiliser le traceur pour afficher l'instance de notre choix ainsi que toutes les instances accessible depuis celle-ci.
Pour cela il faut utiliser la fonction display_instance du module traceur.
Il est maintenant demandé de lire la documentation de cette fonction dans votre interpréteur interactif à l'aide de la fonction help puis de jouer avec.
Correction
Cliquez ici pour révéler la correction.
Voici un petit exemple d'utilisation de la fonction display_instance :
1 2 3 4 5 6 7 8 9 | |
Ce programme n'affiche pas d'image (visualize=False) et spécifie le nom du fichier image qui sera créé (image_name="my_nice_instance" sachant que l'extension .svg est ajoutée automatiquement par le traceur) dont voici le contenu :
Enfin, il est possible d'utiliser le traceur pour afficher un ensemble de variables, donc un ensemble de références, ainsi que toutes les instances accessibles depuis ces variables.
Pour cela il faut utiliser la fonction display_vars du module traceur.
Il est maintenant demandé de lire la documentation de cette fonction dans votre interpréteur interactif à l'aide de la fonction help puis de jouer avec.
Correction
Cliquez ici pour révéler la correction.
Voici un petit exemple d'utilisation de la fonction display_vars :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Ce programme n'affiche pas d'image (visualize=False) et spécifie le nom du fichier image qui sera créé (image_name="my_nice_vars" sachant que l'extension .svg est ajoutée automatiquement par le traceur) dont voici le contenu :
Les variables, qui sont des références, sont affichées en vert.
Le traceur ne peut pas retrouver le nom d'une variable à partir d'une référence quelconque.
C'est pourquoi ces derniers doivent être spécifiés explicitement via la création d'instances de la classe traceur.Variable (c'est le premier paramètre du constructeur).