Pensez en Python

Comment maîtriser la science de l'informatique


précédentsommairesuivant

A. Débogage

Lorsque vous déboguez, vous devez faire la distinction entre les différents types d'erreurs afin de les trouver plus rapidement.

  • Les erreurs de syntaxe sont découvertes par l'interpréteur lorsqu'il traduit le code source en code binaire. Elles indiquent qu'il y a un souci dans la structure du programme. Exemple : l'omission du caractère deux-points à la fin d'une instruction def génère le message quelque peu redondant SyntaxError: invalid syntax.
  • Les erreurs d'exécution sont produites par l'interpréteur si quelque chose se passe mal pendant l'exécution du programme. La plupart des messages d'erreur d'exécution comprennent des informations sur l'endroit où l'erreur est survenue et quelles fonctions étaient exécutées. Exemple : une récursion infinie provoque finalement l'erreur d'exécution « maximum recursion depth exceeded » (profondeur de récursivité maximale dépassée).
  • Les erreurs sémantiques sont des problèmes avec un programme qui s'exécute sans produire des messages d'erreur, mais qui ne fait pas ce qu'il devrait. Exemple : une expression peut ne pas être évaluée dans l'ordre que vous attendiez, ce qui donne un résultat incorrect.

La première étape dans le débogage est de déterminer de quel type d'erreur il s'agit. Bien que les sections suivantes soient organisées par type d'erreurs, certaines techniques sont applicables à plusieurs situations.

A-1. Erreurs de syntaxe

Les erreurs de syntaxe sont généralement faciles à corriger une fois que vous avez compris leur nature. Malheureusement, les messages d'erreur ne sont souvent pas très explicites. Les messages les plus courants sont SyntaxError: invalid syntax et SyntaxError: invalid token, aucun des deux n'offrant assez d'informations.

D'un autre côté, le message vous dit à quel endroit du programme est survenu le problème. En fait, il vous indique l'endroit où Python a remarqué un problème, qui n'est pas nécessairement l'endroit où se trouve l'erreur. Parfois, l'erreur se trouve dans le code précédant l'emplacement indiqué par le message d'erreur, souvent sur la ligne précédente.

Si vous construisez le programme de façon incrémentielle, vous devriez avoir une bonne idée de l'endroit où est l'erreur. Elle sera sur la ou les dernière(s) ligne(s) que vous avez ajoutée(s).

Si vous copiez le code à partir d'un livre, commencez par comparer très attentivement votre code à celui du livre. Vérifiez chaque caractère. En même temps, rappelez-vous que le livre peut contenir des erreurs, donc si vous voyez quelque chose qui ressemble à une erreur de syntaxe, c'est peut-être le cas.

Voici quelques façons d'éviter les erreurs de syntaxe les plus courantes.

  1. Assurez-vous que vous n'utilisez pas un mot-clé de Python comme nom de variable.
  2. Vérifiez qu'il y a un caractère deux-points à la fin de l'en-tête de chaque instruction composée, notamment les instructions for, while, if et def.
  3. Assurez-vous que toutes les chaînes de caractères dans le code sont entre guillemets appariés. Assurez-vous que tous les guillemets sont « droits », pas des virgules « culbutées » ou des guillemets « français » en forme de chevrons.
  4. Si vous avez des chaînes de caractères multilignes entre guillemets triples (simples ou doubles), assurez-vous que vous avez terminé correctement la chaîne de caractères. Une chaîne non terminée peut provoquer une erreur invalid token (symbole invalide) à la fin de votre programme, ou le code qui la suit peut être traité comme une chaîne de caractères jusqu'aux guillemets de la chaîne suivante. Dans le second cas, cela pourrait ne produire aucun message d'erreur !
  5. Un opérateur ouvert et non fermé, comme (, {, ou [, laisse Python continuer à la ligne suivante comme faisant partie de l'instruction en cours. Généralement, une erreur se produit presque immédiatement dans la ligne suivante.
  6. Vérifiez si vous n'avez pas fait l'erreur classique d'utiliser = au lieu de == à l'intérieur d'une instruction conditionnelle.
  7. Vérifiez l'indentation pour vous assurer qu'elle s'aligne comme il faut. Python peut gérer des espaces et tabulations, mais si vous les mélangez, cela peut causer des problèmes. La meilleure façon d'éviter ce problème est d'utiliser un éditeur de texte qui connaît Python et génère une indentation cohérente.
  8. Si vous avez des caractères non ASCII dans le code (y compris dans les chaînes de caractères et les commentaires), cela peut causer un problème, même si Python 3 gère habituellement les caractères non ASCII. Soyez prudent si vous collez du texte provenant d'une page web ou d'une autre source.

Si rien de tout cela ne fonctionne, passez à la section suivante…

A-1-1. Je n'arrête pas de modifier et rien ne change

Si l'interpréteur dit qu'il y a une erreur et vous ne la voyez pas, il se peut que vous et l'interpréteur n'analysiez pas le même code. Vérifiez votre environnement de programmation pour vous assurer que le programme que vous éditez est celui que Python essaie d'exécuter.

Si vous n'êtes pas sûr, essayez de mettre une erreur de syntaxe évidente et délibérée au début du programme. Maintenant, lancez à nouveau. Si l'interpréteur ne trouve pas la nouvelle erreur, vous n'exécutez pas le nouveau code.

Il y a quelques causes possibles.

  • Vous avez modifié le fichier et oublié d'enregistrer les modifications avant de l'exécuter à nouveau. Certains environnements de programmation font cela pour vous, mais tous ne le font pas.
  • Vous avez changé le nom du fichier, mais vous exécutez toujours l'ancien nom.
  • Quelque chose dans votre environnement de développement est configuré de manière incorrecte.
  • Si vous écrivez un module et utilisez import, assurez-vous de ne pas donner à votre module le nom d'un des modules standard de Python.
  • Si vous utilisez import pour lire un module, rappelez-vous que vous devez redémarrer l'interpréteur ou utiliser reload pour lire un fichier modifié. Si vous importez à nouveau le module, il ne fait rien.

Si vous vous retrouvez coincé et vous ne pouvez pas comprendre ce qui se passe, une approche consiste à recommencer avec un nouveau programme comme « Hello World ! », et vous assurer que vous pouvez exécuter un programme connu. Puis, ajoutez progressivement les morceaux du programme original au nouveau programme.

A-2. Erreurs d'exécution

Une fois que votre programme est syntaxiquement correct, Python peut le lire et au moins commencer l'exécution. Qu'est-ce qui pourrait mal tourner ?

A-2-1. Mon programme ne fait absolument rien

Ce problème est plus courant lorsque votre fichier est constitué de fonctions et de classes, mais en fait aucune fonction n'est invoquée pour démarrer l'exécution. Cela peut être intentionnel si vous prévoyez juste d'importer ce module pour fournir des classes et des fonctions.

Si cela n'est pas intentionnel, assurez-vous qu'il existe un appel de fonction dans le programme, et assurez-vous que le flux d'exécution l'atteint (voir « Flux d'exécution » ci-dessous).

A-2-2. Mon programme s'arrête et ne fait plus rien

Si un programme s'arrête et semble ne rien faire, il est « suspendu ». Souvent, cela signifie qu'il est pris dans une boucle infinie ou une récursion infinie.

  • Si vous soupçonnez qu'une boucle particulière de votre programme est peut-être à l'origine du problème, ajoutez immédiatement avant la boucle une instruction d'affichage disant « entrer dans la boucle » et une autre immédiatement après, disant « sortir de la boucle ». Exécutez le programme. Si vous voyez le premier message et pas le second, vous avez une boucle infinie. Allez à la section « Boucle infinie » ci-dessous.
  • Presque toujours, une récursion infinie va laisser le programme s'exécuter pendant un certain temps, puis une erreur « RuntimeError: Maximum recursion depth exceeded » se produira. Si cela arrive, allez à la section « Boucle infinie » ci-dessous. Si vous n'avez pas cette erreur, mais pensez qu'il y a un problème avec une méthode ou une fonction récursive, vous pouvez toujours utiliser les techniques dans la section « Récursion infinie ».
  • Si aucune de ces étapes ne fonctionne, commencez à tester d'autres boucles et d'autres fonctions et méthodes récursives.
  • Si cela ne fonctionne pas, alors il est possible que vous ne compreniez pas le flux d'exécution de votre programme. Allez à la section « Flux d'exécution » ci-dessous.

Boucle infinie

Si vous pensez que vous avez une boucle infinie et vous pensez que vous savez quelle boucle est la cause du problème, ajoutez à la fin de la boucle une instruction print qui affiche les valeurs des variables dans la condition et la valeur de la condition.

Par exemple :

 
Sélectionnez
while x > 0 and y < 0 :
    # faire quelque chose avec x
    # faire quelque chose avec y

    print('x : ', x)
    print('y : ', y)
    print("condition : ", (x > 0 and y < 0))

Maintenant, lorsque vous exécutez le programme, vous verrez trois lignes affichées à chaque passage dans la boucle. Au dernier tour de la boucle, la condition doit être fausse. Si la boucle continue, vous serez en mesure de voir les valeurs de x et y, et vous pourrez comprendre pourquoi ces variables ne sont pas mises à jour correctement.

Récursion infinie

La plupart du temps, une récursion infinie laisse le programme s'exécuter pendant un certain temps, puis produit une erreur « profondeur maximale de récursivité dépassée ».

Si vous soupçonnez qu'une fonction est à l'origine d'une récursion infinie, assurez-vous qu'il y a un cas de base. Il doit y avoir quelque part une condition qui provoque le retour de la fonction sans faire un appel récursif. Si ce n'est pas le cas, vous devez repenser l'algorithme et identifier un cas de base.

S'il y a un cas de base, mais le programme ne semble pas l'atteindre, ajoutez au début de la fonction une instruction print qui imprime les paramètres. Maintenant, lorsque vous exécutez le programme, vous verrez quelques lignes s'afficher chaque fois que la fonction est invoquée, et vous verrez les valeurs des paramètres. Si les paramètres ne tendent pas à se rapprocher du cas de base, vous aurez quelques idées sur l'origine du problème.

Flux d'exécution

Si vous n'êtes pas sûr de savoir comment le flux d'exécution chemine à travers votre programme, ajoutez des instructions print au début de chaque fonction avec un message comme « entrer dans la fonction toto », où toto est le nom de la fonction.

Maintenant, lorsque vous exécutez le programme, il imprime une trace de chaque fonction invoquée.

A-2-3. Lorsque j'exécute le programme, j'obtiens une exception

Si quelque chose se passe mal lors de l'exécution, Python affiche un message qui inclut le nom de l'exception, la ligne du programme où le problème est survenu, et une trace d'appels.

La trace d'appels identifie la fonction qui est en cours d'exécution, puis la fonction qui l'a appelée, et ensuite la fonction qui a appelé cette dernière, et ainsi de suite. Autrement dit, elle reconstitue la séquence d'appels de fonction qui vous a conduit à l'endroit où vous êtes, y compris le numéro de ligne dans votre fichier où chaque appel apparaît.

La première étape consiste à examiner l'endroit dans le programme où l'erreur est survenue et voir si vous pouvez comprendre ce qui est arrivé. Voici quelques-unes des erreurs d'exécution les plus courantes.

NameError : vous essayez d'utiliser une variable qui n'existe pas dans le contexte actuel. Vérifiez si le nom est orthographié correctement, ou au moins de manière cohérente. Et rappelez-vous que les variables locales sont locales ; vous ne pouvez pas les référencer à l'extérieur de la fonction où elles sont définies.

TypeError : il y a plusieurs causes possibles :

  • vous essayez d'utiliser une valeur de façon incorrecte. Exemple : l'indexation d'une chaîne, d'une liste ou d'un tuple par autre chose qu'un entier ;
  • il y a un décalage entre les éléments d'une chaîne de formatage et les éléments passés pour conversion. Cela peut se produire si le nombre d'éléments ne correspond pas ou si vous tentez une conversion invalide ;
  • vous passez le mauvais nombre d'arguments à une fonction. Pour les méthodes, regardez la définition de la méthode et vérifiez si le premier paramètre est self. Ensuite, regardez l'invocation de la méthode ; assurez-vous que vous invoquez la méthode sur un objet avec le bon type et en fournissant correctement les autres arguments.

KeyError : vous tentez d'accéder à un élément d'un dictionnaire à l'aide d'une clé que le dictionnaire ne contient pas. Si les clés sont des chaînes de caractères, rappelez-vous que les majuscules/minuscules sont importantes.

AttributeError : vous tentez d'accéder à un attribut ou une méthode qui n'existe pas. Vérifiez l'orthographe ! Vous pouvez utiliser la fonction interne vars pour lister les attributs qui existent.

Si une AttributeError indique qu'un objet a NoneType, cela signifie qu'il est None. Donc le problème n'est pas le nom de l'attribut, mais l'objet.

La raison pour laquelle l'objet est None pourrait être le fait que vous avez oublié de renvoyer une valeur d'une fonction ; si vous arrivez à la fin d'une fonction sans rencontrer une instruction return, elle retourne None. Une autre cause fréquente est l'utilisation du résultat d'une méthode de liste, comme sort, qui renvoie None.

IndexError : l'indice que vous utilisez pour accéder à une liste, à une chaîne de caractères ou à un tuple est supérieur à la longueur de la collection moins un. Immédiatement avant l'endroit de l'erreur, ajoutez une instruction print pour afficher la valeur de l'indice et de la longueur du tableau. Le tableau a-t-il la bonne taille ? L'indice a-t-il la bonne valeur ?

Le débogueur de Python (pdb) est utile pour traquer les exceptions, car il vous permet d'examiner l'état du programme immédiatement avant l'erreur. Vous pouvez lire au sujet de pdb sur https://docs.python.org/3/library/pdb.html.

A-2-4. J'ai ajouté tant d'instructions print, je suis submergé par la sortie

Un des problèmes de l'utilisation des instructions d'impression pour débogage est que vous pouvez vous retrouver submergé par les affichages. Il existe deux façons de procéder : simplifier la sortie ou simplifier le programme.

Pour simplifier la sortie, vous pouvez supprimer ou mettre en commentaires les instructions print qui ne sont pas utiles, ou les combiner, ou formater la sortie de sorte qu'elle soit plus facile à comprendre.

Pour simplifier le programme, il existe plusieurs choses que vous pouvez faire. Tout d'abord, réduire le problème sur lequel travaille le programme. Par exemple, si vous faites une recherche dans une liste, recherchez dans une petite liste. Si le programme prend des données en entrée de l'utilisateur, donnez-lui la saisie la plus simple qui provoque le problème.

Deuxièmement, nettoyez le programme. Supprimez le code mort et organisez le programme pour le rendre facile à lire autant que possible. Par exemple, si vous soupçonnez que le problème est dans une partie profondément imbriquée du programme, essayez de réécrire cette partie avec une structure plus simple. Si vous soupçonnez une grosse fonction, essayez de la diviser en fonctions plus petites et les tester séparément.

Souvent, le processus de recherche d'un cas de test minimal vous mène au bogue. Si vous remarquez qu'un programme fonctionne dans une situation, mais pas dans une autre, cela vous donne une idée de ce qui se passe.

De la même façon, la réécriture d'un bloc de code peut vous aider à trouver des bogues subtils. Si vous faites un changement et que vous pensez qu'il ne devrait pas affecter le programme, et il le fait, cela peut vous avertir.

A-3. Erreurs sémantiques

À certains égards, les erreurs sémantiques sont les plus difficiles à déboguer, parce que l'interpréteur ne fournit aucune information à propos de ce qui ne va pas. Il n'y a que vous qui sachiez ce que le programme est censé faire.

La première étape est de faire un lien entre le texte du programme et le comportement que vous observez. Vous avez besoin d'une hypothèse sur ce que le programme est en train de faire. Une des choses qui rend cela difficile est que les ordinateurs exécutent le code si vite.

Vous voudrez souvent pouvoir ralentir le programme à une vitesse humaine, et avec quelques débogueurs vous le pouvez. Mais le temps qu'il faut pour insérer quelques instructions print bien placées est souvent court par rapport à la mise en place du débogueur, à l'insertion et à la suppression des points d'arrêt et au parcours du programme « pas à pas » vers l'endroit où l'erreur se produit.

A-3-1. Mon programme ne fonctionne pas

Vous devriez vous poser ces questions.

  • Y a-t-il quelque chose que le programme était censé faire, mais qui ne semble pas se faire ? Trouvez la section du code qui exécute cette fonction et assurez-vous qu'elle s'exécute quand vous pensez qu'elle le devrait.
  • Se passe-t-il quelque chose qui ne devrait pas arriver ? Trouvez dans votre programme le code qui appelle cette fonction et regardez si elle s'exécute quand elle ne le devrait pas.
  • Est-ce qu'une section de code produit un effet qui n'est pas celui que vous attendiez ? Assurez-vous que vous comprenez le code en question, surtout s'il implique des fonctions ou des méthodes dans d'autres modules Python. Lisez la documentation des fonctions que vous appelez. Essayez-les en écrivant des cas de tests simples et en vérifiant les résultats.

Pour programmer, vous avez besoin d'un modèle mental du fonctionnement des programmes. Si vous écrivez un programme qui ne fait pas ce que vous attendez, souvent le problème ne réside pas dans le programme ; il est dans votre modèle mental.

La meilleure façon de corriger votre modèle mental est de découper le programme en composants individuels (généralement les fonctions et les méthodes) et de tester chaque composant indépendamment. Une fois que vous trouvez la différence entre votre modèle et la réalité, vous pouvez résoudre le problème.

Bien sûr, vous devriez construire et tester les composants au fur et à mesure que vous développez le programme. Si vous rencontrez un problème, il devrait y avoir seulement une petite quantité de nouveau code qui n'est pas encore testé et connu comme correct.

A-3-2. J'ai une grosse expression touffue qui ne fait pas ce que j'attends d'elle

Écrire des expressions complexes est très bien tant qu'elles restent lisibles, mais elles peuvent être difficiles à déboguer. Il est souhaitable de décomposer une expression complexe en une série d'affectations à des variables temporaires.

Par exemple, l'expression :

 
Sélectionnez
self.mains[i].ajouterCarte(self.mains[self.trouverVoisin(i)].popCarte())

peut être réécrite ainsi :

 
Sélectionnez
voisin = self.trouverVoisin(i)
carteChoisie = self.mains[voisin].popCarte()
self.mains[i].ajouterCarte(carteChoisie)

La version explicite est plus facile à lire parce que les noms de variables fournissent des renseignements additionnels, et elle est plus facile à déboguer parce que vous pouvez vérifier les types des variables intermédiaires et afficher leurs valeurs.

Un autre problème qui peut arriver avec de grosses expressions est que l'ordre d'évaluation n'est peut-être pas celui que vous attendiez. Par exemple, si vous traduisez l'expression x / 2 π en Python, vous pourriez écrire :

 
Sélectionnez
y = x / 2 * math.pi

Cela n'est pas correct parce que la multiplication et la division ont la même priorité et sont évaluées de gauche à droite. Donc, cette expression calcule x π / 2.

Une bonne façon de déboguer des expressions est d'ajouter des parenthèses pour rendre explicite l'ordre d'évaluation :

 
Sélectionnez
y = x / (2 * math.pi)

Chaque fois que vous n'êtes pas sûr de l'ordre de l'évaluation, utilisez des parenthèses. Non seulement le programme va être correct (dans le sens de faire ce que vous aviez l'intention de lui demander), il sera également plus lisible pour d'autres personnes qui n'ont pas mémorisé l'ordre de précédence des opérateurs.

A-3-3. J'ai une fonction qui ne renvoie pas ce que j'attends

Si vous avez comme valeur de retour une expression complexe, vous n'avez pas la possibilité d'afficher le résultat avant de le renvoyer. Encore une fois, vous pouvez utiliser une variable temporaire. Par exemple, au lieu de :

 
Sélectionnez
return self.mains[i].enleverEgales()

vous pourriez écrire :

 
Sélectionnez
compter = self.mains[i].enleverEgales()
return compter

Maintenant, vous avez la possibilité d'afficher la valeur de compter avant de la renvoyer.

A-3-4. Je me retrouve complètement coincé et j'ai besoin d'aide

Tout d'abord, essayez de vous éloigner de l'ordinateur pendant quelques minutes. Les ordinateurs émettent des ondes maléfiques qui affectent le cerveau, ce qui provoque les symptômes suivants :

  • frustration et rage ;
  • croyances superstitieuses (« l'ordinateur me déteste ») et pensées magiques (« le programme ne fonctionne que quand je porte ma casquette à l'envers ») ;
  • la « programmation errante aléatoire » (la tentative de programmer en écrivant chaque programme possible et de choisir celui qui fait ce qu'on souhaite).

Si vous vous retrouvez à souffrir d'un de ces symptômes, levez-vous et allez faire une petite promenade. Lorsque vous vous êtes calmé, réfléchissez au programme. Qu'est-ce qu'il fait ? Quelles sont les causes possibles de ce comportement ? À quand remonte la dernière fois que votre programme fonctionnait, et qu'avez-vous modifié depuis ?

Parfois, il faut juste du temps pour trouver un bogue. Je trouve souvent des bogues quand je suis loin de l'ordinateur et je laisse mon esprit vagabonder. Certains des meilleurs endroits pour trouver des bogues sont dans les trains, sous la douche et dans le lit, juste avant de vous endormir.

A-3-5. Mais non, j'ai vraiment besoin d'aide

Cela arrive. Même les meilleurs programmeurs se retrouvent parfois coincés. Parfois, vous avez travaillé sur un programme si longtemps que vous ne pouvez pas voir l'erreur. Vous avez besoin d'une nouvelle paire d'yeux.

Avant de demander l'aide de quelqu'un d'autre, assurez-vous que vous êtes prêt. Votre programme doit être aussi simple que possible, et vous devriez travailler avec la plus petite quantité de données en entrée qui provoque l'erreur. Vous devriez avoir des instructions print aux bons endroits (et la sortie qu'elles produisent doit être compréhensible). Vous devez comprendre le problème assez bien pour le décrire de façon concise.

Lorsque vous demandez à quelqu'un de vous aider, assurez-vous de pouvoir lui donner l'information dont il a besoin :

  • s'il y a un message d'erreur, quel est-il et quelle partie du programme indique-t-il ?
  • quelle est la dernière chose que vous avez faite avant cette erreur ? Quelles sont les dernières lignes de code que vous avez écrites, ou quel est le nouveau cas de test qui échoue ?
  • qu'avez-vous essayé jusqu'à présent, et qu'avez-vous appris ?

Lorsque vous trouvez le bogue, prenez une seconde pour réfléchir à ce que vous auriez pu faire pour le trouver plus vite. La prochaine fois que vous verrez quelque chose de similaire, vous serez en mesure de trouver le bogue plus rapidement.

Rappelez-vous, l'objectif n'est pas seulement de faire fonctionner le programme. L'objectif est d'apprendre comment faire fonctionner le programme.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Licence Creative Commons
Le contenu de cet article est rédigé par Allen B. Downey et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2016 Developpez.com.