cours python gratuit d’initiation: LA GESTION DES EXCEPTIONS
C’est le cours python numéro 19 de la série des cours d’initiation à python.
EN SAVOIR PLUS SUR LES ERREURS D’EXÉCUTION
Nous savons maintenant ce qu’est une erreur d’exécution : si, lors de l’exécution d’un programme, nous demandons à Python de faire quelque chose d’illégal (par exemple, diviser par zéro, utiliser un index inexistant dans une liste ou appeler une fonction avec un nombre d’arguments incorrect) Python bloque immédiatement l’exécution, quitte le programme et signale l’erreur. Ce comportement est acceptable lors du développement d’un programme, mais serait plutôt gênant dans un programme “sérieux”. Il est très facile de provoquer une erreur d’exécution avec la mauvaise entrée : voici un exemple :
noms = ["Gino", "Peppe", "Carlo", "Ciccio"] n = int (input ("J'ai pensé à quatre noms. Lequel voulez-vous connaître ?")) n - = 1 # rappelez-vous que les indices vont de 0 à 3 print (noms [n])
Lorsque nous exécutons ce programme, nous pouvons heureusement provoquer une ValueError en tapant “xyz” à l’invite (Python doit traduire notre chaîne en un entier, mais la chaîne n’a aucune signification). Ou nous pouvons provoquer une IndexError en tapant “10” (dans l’instruction suivante, nous demandons à Python de lire le dixième élément d’une liste avec seulement quatre éléments).
La deuxième erreur pourrait être évitée en utilisant la fonction input_int () que nous avons programmée dans la leçon 17, mais pour la première il semble n’y avoir aucune solution : si un programme commercial plante pour un problème aussi stupide, personne ne serait probablement prêt à dépenser de l’argent pour l’acheter !
Bien sûr, il existe une solution : l’arrêt du programme n’est que le comportement par défaut, mais Python (comme tous les langages plus avancés) permet de gérer les erreurs d’exécution, en s’assurant que, lorsqu’elles surviennent, les instructions définies par le programmeur sont exécutées. servent généralement à corriger l’erreur) et le programme peut continuer.
INSTRUCTIONS try … except
En langage informatique le terme « exception » (en anglais : exception) a progressivement remplacé le terme « erreur », si bien qu’aujourd’hui ils sont tous deux utilisés comme synonymes. Modifions le programme précédent comme ceci :
names = ["Gino", "Peppe", "Carlo", "Ciccio"] try: n = int (input ("I've thought four names. Which one do you want to know?")) n - = 1 # remember that the indices range from 0 to 3 print (names [n]) except: print ("Invalid input!")
Lancez-le et essayez d’écrire quelque chose à l’invite qui n’a pas de sens : le programme ne vous donnera plus d’erreur, mais écrira “Entrée invalide”. La paire d’instructions try … except est utilisée pour gérer les exceptions. Ils doivent être au même niveau d’indentation et doivent tous deux être suivis d’un bloc de code (indenté). Python essaie d’abord d’exécuter le bloc try ; si une erreur s’y produit, il saute immédiatement (sans se terminer) au bloc except, qu’il exécute en entier. Si, d’autre part, aucune erreur ne se produit, l’exception est ignorée. Notez que try et except, comme toutes les instructions qui modifient le déroulement du programme, sont suivis de deux-points.
Dans tous les cas, après avoir exécuté l’essai ou l’exclusion, le programme continue avec la première instruction suivante au même niveau d’indentation, et ainsi nous pouvons éviter qu’une erreur ne plante le programme.
Ainsi, dans notre exemple, Python commence par exécuter le bloc try ; si une erreur se produit dans l’entrée (), il saute les deux autres lignes du try et exécute immédiatement le bloc except. Si à la place nous entrons une entrée correcte, Python exécute tous les essais et saute l’exception (et n’écrit donc pas “Entrée invalide”).
Voyons maintenant comment nous pouvons répéter l’entrée jusqu’à ce que nous obtenions le résultat souhaité. Modifier comme suit :
names = ["Gino", "Peppe", "Carlo", "Ciccio"] while True: try: n = int (input ("I've thought four names. Which one do you want to know?")) n - = 1 # remember that the indices range from 0 to 3 print (names [n]) break except: print ("Invalid input!")
Les deux blocs try … except sont maintenant insérés à l’intérieur d’une boucle while infinie : si une erreur se produit dans le try, le programme sautera à l’exclusion, avertissant d’une entrée incorrecte et répétant le while ; par contre, si tout se passe bien, le break est exécuté à la fin de l’essai et la boucle est sortie en continuant le programme.
UNE SYNTAXE PLUS COMPLIQUEE
L’instruction except peut être suivie du nom d’une ou plusieurs exceptions (par exemple NameError, TyperError, ValueError …) séparées par des virgules (attention à la correspondance sensible à la casse). Dans ce cas, il n’intercepte que ce type d’exceptions, tandis que les autres ne sont pas gérées. Après un essai, il peut y en avoir plus, sauf pour gérer différentes erreurs différemment.
names = ["Gino", "Peppe", "Carlo", "Ciccio"] while True: try: n = int (input ("I've thought four names. Which one do you want to know?")) n - = 1 # remember that the indices range from 0 to 3 print (names [n]) break except ValueError: # is executed if we entered a nonsense string print ("You must enter a number!") except IndexError: # is executed if we entered a wrong number print ("You entered an invalid number!")
Il faut dire que try…except n’est pas un moyen “miracle” pour masquer nos erreurs de programmation, mais ne doit être utilisé que pour gérer les exceptions que le programmeur a prévues. C’est pourquoi les programmeurs expérimentés considèrent qu’il est dangereux de l’utiliser sauf seul, car il pourrait également “attraper” les exceptions qui résultent de nos erreurs de programmation. Parfois, vous mettez un except comme dernière instruction, mais cela doit être considéré comme une sorte de “dernier recours” afin de ne pas planter le programme :
: names = ["Gino", "Peppe", "Carlo", "Ciccio"] while True: try: n = int (input ("I've thought four names. Which one do you want to know?")) n - = 1 # remember that the indices range from 0 to 3 print (names [n]) break except ValueError: print ("You must enter a number!") except IndexError: print ("You entered an invalid number!") except: print ("Oops! Unexpected error! I don't know what happened")
Le dernier sauf ne sera probablement jamais exécuté : si c’était le cas, nous aurions probablement à exécuter le programme avec le débogueur pour comprendre quel type d’erreur s’est produit (et le gérer en conséquence).
De plus, le try peut également être suivi d’un else (qui, s’il est présent, va après tous les except), qui n’est exécuté que si aucune erreur ne s’est produite dans le bloc try. La pratique courante est de limiter les instructions dans le try au minimum (éventuellement uniquement celles qui pourraient potentiellement donner une erreur que nous avons prévue) et d’ajouter les autres instructions dans le else : ceci aussi afin de limiter au maximum le possibilité que dans l’essai se produise une exception inattendue, “masquant” notre erreur de programmation. Enfin, voici notre programme mis à jour :
names = ["Gino", "Peppe", "Carlo", "Ciccio"] while True: try: n = int (input ("I've thought four names. Which one do you want to know?")) n - = 1 # remember that the indices range from 0 to 3 print (names [n]) except ValueError: print ("You must enter a number!") except IndexError: print ("You entered an invalid number!") except: print ("Oops! Unexpected error! I don't know what happened") else: break
Seules les deux instructions potentiellement dangereuses (l’entrée () qui pourrait provoquer une ValueError et l’impression (nom [n]) qui pourrait donner une IndexError) ont été laissées dans le try, tandis que le break a été déplacé vers le else.
EXCEPTIONS ET FONCTIONS
Le bloc try … except gère également les exceptions qui se produisent dans les fonctions appelées dans le try :
from math import * try: n = float (input ("Write a number")) print (sqrt (n)) print (log (n)) except ValueError: print ("Incorrect input")
Si nous entrons 0 ou un nombre négatif, les fonctions sqrt() ou log() provoqueront une erreur, qui sera interceptée par notre except. Lorsque nous utilisons des fonctions, nous devons donc savoir quelles éventuelles exceptions elles peuvent provoquer et, si nous ne voulons pas qu’elles interrompent l’exécution du programme, nous devons nous soucier de les gérer.
L’instruction pass
Cette instruction… ne fait rien ! Cependant, il est parfois nécessaire dans certaines situations comme celle-ci :
while True: try: n = float (input ("Write a number")) except ValueError: pass else: break
Dans ce cas, si une exception se produit, nous voulons simplement répéter l’invite “Ecrire un nombre”. Nous ne pouvons pas simplement écrire sauf que, comme il doit être suivi d’un bloc de code en retrait, une instruction “ne rien faire” devient nécessaire.
La passe peut être placée n’importe où, sa fonction est principalement de prendre la place d’un bloc en retrait. Il est parfois utilisé provisoirement dans un elif, comme espace réservé pour les cas que nous n’avons pas encore planifiés.
n = int (input ("Write a number")) if (n == 1): print ("This is case 1") make_complicated_calculations_with_1 () elif (n == 2): pass # this case I'll write it later, but an indented block is needed for now elif (n == 3): pass # ditto
L’instruction augmenter
Enfin, nous pouvons volontairement forcer une exception avec l’instruction raise. La syntaxe est
lever exception_name [(paramètre)]
le paramètre est facultatif et, s’il est présent, il s’agit généralement d’une chaîne écrite immédiatement après le nom de l’erreur à titre d’explication. Par exemple, essayez d’écrire dans IDLE :
>>> raise NameError Traceback (most recent call last): File "<pyshell # 8>", line 1, in <module> raise NameError NameError >>> raise NameError ("Wrong Name") Traceback (most recent call last): File "<pyshell # 9>", line 1, in <module> raise NameError ("Wrong Name") NameError: Wrong name
La relance est souvent utilisée dans les fonctions que nous programmons, pour signaler au programme principal que quelque chose s’est mal passé : généralement, si quelque chose d’anormal se produit dans une fonction, le programmeur écrit la relance sans aucun bloc try, donc Python renvoie une erreur et quitte la fonction immédiatement. Par exemple, revenons au programme root.py que nous avons implémenté dans la leçon 15.
Nous avions déjà remarqué que notre programme ne fonctionnait pas si nous demandions la racine d’un nombre négatif (qui n’existe pas de toute façon) et nous avions résolu le problème en arrêtant le calcul et en imprimant un message. Cela peut convenir dans le programme principal, mais n’est pas souhaitable dans une fonction : le programme appelant s’attend à ce que la fonction lui renvoie une valeur, et des erreurs inattendues peuvent se produire si cela ne se produit pas. Mieux vaut lever une exception :
def mysqrt (n): si n <0 : raise ValueError ("mysqrt a été appelé avec un argument négatif") . . .
Ainsi, si vous appelez la fonction avec un nombre négatif comme argument, la fonction se terminera immédiatement et signalera l’erreur au programme principal, qui pourra éventuellement la gérer.
METTEZ À JOUR myinput.py
Revenons à notre module myinput.py et introduisons la gestion des exceptions. Les fonctions “problématiques” sont celles qui convertissent une chaîne en entier : nous avons vu plus haut que si la chaîne n’a pas de sens, une ValueError est obtenue. Voyons par exemple la fonction input_int()
def input_int (prompt, min, max): while True: resp = int (input (prompt)) if resp> = min and resp <= max: return resp print ("Incorrect input")
Changeons-le comme ceci :
Maintenant la fonction fait ce qu’elle a à faire, mais pour un informaticien c’est “esthétiquement moche” de répéter le print() deux fois (la répétition est nécessaire : une ligne est exécutée en cas d’exception, l’autre si le nombre saisi n’est pas dans les bonnes limites). Voici une astuce pour y remédier :
def input_int (prompt, min, max): while True: try: resp = int (input (prompt)) if resp <min or resp> max: raise ValueError return resp except ValueError: print ("Incorrect input")
De cette façon, si l’utilisateur a entré un nombre qui n’est pas dans les limites correctes, nous générons une erreur et provoquons l’exécution de l’exception.
Nous signalons explicitement qu’après l’amélioration des fonctions, tous les programmes qui utilisent ce module seront automatiquement mis à jour à chaque fois que nous les lancerons. Si nous avions écrit les fonctions directement dans les programmes, il faudrait maintenant rechercher tous les fichiers dans lesquels nous avons utilisé ces fonctions et les modifier. C’est une autre raison en faveur du regroupement des fonctions dans des modules séparés.