Découvrez les stack traces
La plupart du temps, Python vous écoute. Il le fait évidemment quand vous écrivez du code et qu’il l'exécute en conséquence. Mais saviez-vous que Python vous parle également, notamment par l’intermédiaire des stack traces ? Je vous propose d’en apprendre plus sur ces derniers.
Si vous écrivez quelque chose que l’interpréteur de Python ne comprend pas parce que ce n’est pas en langage « Python », ou si vous demandez à Python de faire quelque chose d’impossible comme importer un module qui n’existe pas, il explose ! 🤯
Je plaisante évidemment. En fait, Python s’arrête tout simplement et génère ce qu’on appelle une exception, on dit qu’il lève une exception. Une exception est un type d’erreur connu du langage Python. Autrement dit, Python sait à quelle famille appartient cette erreur. Une exception est levée dans ce qu’on appelle une stack trace. Il s’agit d’un message qui commence souvent par la ligneTraceback (most recent call last)
:
Dans cet exemple, la stack trace nous fait part d’une NameError
, une des exceptions natives en Python.
Bien souvent, les stack traces contiennent déjà tout ce qu’il vous faut pour trouver l’erreur et débugger votre code. Mais parfois l’information donnée n’est qu’une simple indication sur le type du bug et non pas sur la manière dont il se manifeste dans notre code. L’endroit où l’exception est levée ne correspondra donc pas forcément à la cause principale du bug, mais plutôt à l’endroit où ce dernier devient un problème pour Python. Au fur et à mesure que vous enlèverez les bugs, la stack trace vous affichera les suivants.
Je reconnais néanmoins qu’il peut être intimidant de les lire, surtout quand les stack traces contiennent beaucoup de lignes. 🤯 Je vous partage quelques astuces sur comment procéder pour les lire efficacement.
Commencez par le bas de la stack trace
Cette partie vous indique explicitement le type de l’erreur, qui est bien souvent le nom de l’exception. Elle vous indique aussi d’autres informations, telles que la variable à incriminer, voire une explication sur la manière de résoudre l’erreur.
Ensuite lisez le haut de la stack trace
Cette partie reprend la zone du code où se trouve l’erreur. Elle ajoute même une flèche pour indiquer la ligne exacte où se trouve l’erreur (c’est pas gentil ça ? 😊).
Cette partie est parfois très longue parce qu’elle indique des éléments de code qui font référence aux modules, classes ou fonctions utilisés de manière explicite (c’est à dire, directement par vous) ou implicite (utilisés à l’intérieur d'une classe ou d’une fonction que vous manipulez) dans notre programme.
Néanmoins, la seule partie qui vous intéresse vraiment concerne le code que vous avez écrit. En effet, vous ne pouvez pas modifier le code des modules que vous n’avez pas créé. Cependant, plus vous serez capable de lire l’ensemble du stack trace, plus vous améliorerez vos compétences de programmeur.
Identifiez les exceptions Python
Plongeons dès maintenant dans les exceptions qu’on rencontre très souvent dans une vie de programmeur Python. Je vous montrerai aussi un exemple de chacune de ces exceptions.
Dans le screencast ci-dessous, je vais illustrer avec du code les exceptions les plus courantes de Python. Nous verrons un exemple des erreurs suivantes : AttributeError
, KeyError
, IndexError
, NameError
, et SyntaxError
. 👇
Pour résumer, nous venons de voir les erreurs :
AttributeError
: Chaque type de variable Python est un type d’objet différent. On accède à un attribut d’un objet en écrivantobjet.attribut
et à une méthode en écrivantobjet.methode()
. Si on essaie d’accéder à un attribut ou une méthode inexistante dans la classe de l’objet on aura cette erreur.KeyError
: On fait appel à une clé inexistante de notre objet (liste, dictionnaire, dataframe…).IndexError
: On cherche la position d’un objet qui n’existe pas, par exemple l’élément à la position 4 d’une liste qui n’en contient que 3.NameError
: On fait appel à une variable ou fonction inconnue.SyntaxError
: L’erreur la plus courante, qui apparaît quand on fait une faute de grammaire propre au langage Python.
Regardons maintenant TypeError
, ValueError
, FileNotFoundError
et ModuleNotFoundError
:
Voici un récapitulatif des erreurs vues dans ce dernier screencast :
TypeError
: Plusieurs causes sont possibles, mais elles tournent autour d’un problème de type de variable. Par exemple, on fait une conversion de type interdite.ValueError
: Elle aussi a de nombreuses causes possibles. Globalement, il s’agit d’un problème de valeur, même si le type est correct. Par exemple, si on essaie de convertir une chaîne de caractère contenant autre chose que des chiffres enint
.FileNotFoundError
: Le fichier auquel nous faisons référence n’existe pas ou est mal ciblé.ModuleNotFoundError
: Le module auquel nous faisons référence est inconnu. Il faut l’importer ou le créer.
Dans les chapitres suivants, nous apprendrons à débugger ces exceptions.
Utilisez print pour débugger
La fonction print()
permet de débugger sans message d’erreur.
Vous serez sûrement d’accord pour dire que les stack traces sont très pratiques. Néanmoins, il peut arriver que votre programme contienne une erreur sans qu’aucune exception ne soit levée.
Quoi ? Une erreur sans message d’erreur ? 🤨 Je croyais qu’on venait de voir tous les types d’erreurs possibles dans Python.
En effet, Python n’est pas dans votre tête et, quand vous faites une erreur de logique qui respecte la syntaxe du langage, il ne peut pas vous le dire. Prenons l’exemple du code ci-dessous :
i = 0
while i < -10:
print(i)
i -= 1
L’objectif est d’afficher des chiffres, en commençant par -1 et en terminant par -10, mais ici il y a une erreur puisque le résultat n’est pas celui qui est obtenu. 🧐
C’est à vous de jouer le rôle du stack trace. Comment ? En utilisant la fonction print
. Nous pouvons, par exemple, placer un print
avant la boucle pour vérifier que la condition est bien respectée :
Le résultat de la condition est faux, ce qui explique que le programme ne rentre pas dans la boucle, ce qui serait le cas si la condition était vraie. Ici, nous devons modifier la comparaison pour indiquer i > -10
, ce qui donnera donc 0 supérieur à -10, ce qui est vrai.
À ce stade le programme fonctionne, mais les chiffres renvoyés vont de 0 à -9 et non pas de -1 à -10. Nous allons placer un autre print
pour voir comment évolue la variable i
avant, mais aussi après la modification de sa valeur.
En observant les valeurs successives de i
, nous constatons qu’il faut placer le print
après la modification de la variable i
pour obtenir les valeurs allant de -1 à -10. Le programme donnera donc ceci :
Comme nous venons de le voir, la fonctionprint
peut être un débuggeur efficace car elle nous permet de suivre l’évolution des variables d’un programme.
Faites usage de la fonction help
Pour débugger, vous pouvez utiliser une autre fonction native de Python : help
. Comme son nom l'indique, cette fonction vous aide à obtenir des informations sur un élément du langage, qu’il s’agisse d’une classe, d’un objet, d’une fonction ou d’une variable. Plus précisément, elle affiche le contenu des docstrings de la classe ou de la fonction.
Les docstrings ? Vous pouvez me rappeler ce que c’est déjà ?
Bien sûr, c’est demandé si gentiment. 😉 Un docstring est une chaîne de caractère écrite entre trois guillemets, simples ou doubles, comme le montre les exemples ci-dessous. Il est utilisé pour décrire de manière plus ou moins détaillée une fonction ou une classe à l’attention de ses futurs utilisateurs. La quasi-totalité des fonctions que vous utilisez contiennent des docstrings. D’ailleurs, ces derniers sont souvent des résumés du contenu de la documentation officielle de l’élément que vous utilisez :
Vous pouvez également passer en paramètre de la fonction help
des fonctions comme print
, type
et même help
comme dans l’image ci-dessous :
Exercez vous
Je vous propose de vous entraîner sur le projet logiciel que vous retrouverez sur notre repository Github juste ici. Après avoir exécuté le fichier main.py
vous tomberez sur plusieurs exceptions que j’ai reproduites ci-dessous.
Je vous propose d’essayer de trouver l’origine de ces exceptions par vous même. Ensuite, regardez la vidéo pour découvrir avec moi leurs causes potentielles.
Exemple 1
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/tkinter/__init__.py", line 297, in _get_default_root
raise RuntimeError(f"Too early to {what}: no default root window")
RuntimeError: Too early to create image: no default root window
Exemple 2
Traceback (most recent call last):
File "/main.py", line 81, in <module>
texte1 = Label(fenetre, text="Vous", font=("Arial", "20", "bold"))
NameError: name 'Label' is not defined
Exemple 3
Traceback (most recent call last):
File "/main.py", line 106, in <module>
zero = PhotoImage(file ='zero.jpg')
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/tkinter/__init__.py", line 4064, in __init__
Image.__init__(self, 'photo', name, cnf, master, **kw)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/tkinter/__init__.py", line 4009, in __init__
self.tk.call(('image', 'create', imgtype, name,) + options)
_tkinter.TclError: couldn't recognize data in image file "zero.jpg"
Dans le screencast, nous allons explorer les exceptions levées par notre projet « Pierre Feuille Ciseaux ». 🤖
Dans ce screencast nous avons parcouru les exceptions levées par le programme et expliqué leurs causes potentielles :
Nous avons vu que nous devons créer la fenêtre tkinter avant d’y ajouter des images avec la fonction
PhotoImage
.Pour être ajoutées, ces images doivent respecter les formats indiqués dans la documentation.
L’erreur liée à la fonction Label est quant à elle dû au fait qu’elle n’est pas importée.
Vous pouvez consulter le code du projet logiciel sur le repository Github.
En résumé
Un stack trace est un message que Python affiche pour nous donner des indications sur une exception qui a été levée.
Une exception est une erreur dans le programme qui bloque son exécution, et en Python il existe de nombreuses exceptions natives du langage.
La fonction
print
peut être utilisée dans un contexte de débugging pour traquer le comportement des variables dans un programme.La fonction
help
permet d’obtenir des informations sur une classe, un objet ou une fonction. Ces informations sont décrites dans des docstrings.
Maintenant que vous avez maîtrisé l’utilisation du stack trace, de print et de la fonction help dans un contexte de débugging, je vous propose d’utiliser deux modules dédiés à cette activité. Rendez-vous au prochain chapitre !