Exceptions A : pourquoi gérer les erreurs, try et except
- Expliquer ce qu’est une Exception et ce qui arrive au programme quand elle n’est pas gérée
- Écrire un bloc
try/exceptqui attrape une erreur et la gère - Gérer séparément plusieurs types d’Exceptions, et obtenir les détails de l’erreur avec
as - Comprendre pourquoi un
except« vide » qui attrape tout est une habitude dangereuse
Qu’allons-nous apprendre ?
- Ce qu’est une Exception.
- Pourquoi un vrai logiciel doit gérer les erreurs et ne pas planter.
- La syntaxe
try/except. - Une gestion différente pour différents types d’erreurs.
Pourquoi en avons-nous besoin ?
Imaginez un petit serveur de calculatrice : un client envoie "hello" au lieu d’un nombre, et tout le serveur plante. Tous les clients qui viennent ensuite reçoivent « Connection refused ».
Dans le monde réel :
- Un utilisateur tape « abc » dans un champ d’âge. L’application ne doit pas se fermer.
- Un fichier que le programme essaie de lire a été supprimé. Il doit le signaler, pas planter.
- Internet a coupé au milieu d’une requête. Il vaut la peine de réessayer, ou d’afficher un message.
Les erreurs arriveront : une mauvaise saisie, des fichiers manquants, un réseau instable. La seule question est ce que fait le logiciel quand elles arrivent.
Explication à partir des bases
Qu’est-ce qu’une Exception ?
Une Exception est un objet que Python « lance » (Raise) quand une Runtime Error se produit. Toutes les erreurs que nous avons vues hier (ValueError, ZeroDivisionError, TypeError…) sont des Exceptions.
Quand une Exception est lancée :
- Python arrête immédiatement la ligne en cours.
- Il cherche quelqu’un qui « attrapera » (Catch) l’erreur.
- Si personne ne l’attrape, le programme plante et un Traceback est affiché.
try / except est le moyen d’attraper.
Un funambule (votre code) peut tomber (une Exception). Sans filet, la chute est la fin du spectacle (un plantage). try = « le numéro se fait au-dessus d’un filet ». except = le filet, qui rattrape la chute, et ensuite vous décidez quoi faire : réessayer, prévenir le public, ou passer au numéro suivant.
Comment ça marche
try:— « essaie d’exécuter ce bloc ».except SomeError:— « si uneSomeErrorest lancée dans letry, saute ici et exécute ce bloc au lieu de planter ».- Si aucune erreur n’a été lancée, le bloc
exceptest sauté. - Après le
try/except, le programme continue normalement.
Syntaxe
try:
# code that might fail
except ErrorType:
# what to do if ErrorType happened
# several error types, each handled differently
try:
...
except ValueError:
...
except ZeroDivisionError:
...
# get the error object itself (its message)
except ValueError as error:
print(error)
# same handling for several types
except (ValueError, TypeError):
...Premier exemple de code
week-2/safe_age.py :
text = input("Enter your age: ")
try:
age = int(text)
print("Next year you will be", age + 1)
except ValueError:
print("That is not a whole number:", text)
print("Program continues normally.")Exécution 1 (saisie valide) :
Enter your age: 30
Next year you will be 31
Program continues normally.Exécution 2 (saisie invalide) :
Enter your age: abc
That is not a whole number: abc
Program continues normally.Analyse du code ligne par ligne
| Ligne | Exécution 1 (30) | Exécution 2 (abc) |
|---|---|---|
text = input(...) | text = "30" | text = "abc" |
try: | Entre dans le bloc | Entre dans le bloc |
age = int(text) | Réussit : age = 30 | Une ValueError est lancée, et Python saute directement au except |
print("Next year...") | Affiché | Sauté. On n’y arrive jamais |
except ValueError: | Sauté (pas d’erreur) | Attrapée ! |
print("That is not...") | Ne s’exécute pas | Affiché |
print("Program continues...") | Affiché | Affiché, et le programme n’a pas planté |
Le point important : dès qu’une erreur est lancée, les lignes restantes du try ne s’exécutent pas. C’est pourquoi on ne met dans le try que ce qui dépend de la réussite.
Un autre exemple
Une calculatrice de division robuste, avec une gestion différente pour chaque type d’erreur et une boucle qui redemande. week-2/safe_divide.py :
def ask_number(prompt):
while True:
text = input(prompt)
try:
return float(text)
except ValueError:
print("Please type a number, for example 12 or 3.5")
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
print("Cannot divide by zero - returning 0")
return 0
x = ask_number("First number: ")
y = ask_number("Second number: ")
print("Result:", divide(x, y))Exemple d’exécution :
First number: ten
Please type a number, for example 12 or 3.5
First number: 10
Second number: 0
Cannot divide by zero - returning 0
Result: 0Analyse :
return float(text)dans untry— si la conversion réussit, la fonction renvoie immédiatement et sort aussi de la boucle. Si elle échoue,exceptaffiche un message, et la boucle redemande. Ainsi, on « oblige » l’utilisateur à une saisie valide.- Chaque fonction attrape l’erreur qui la concerne :
ask_numberattrapeValueError(la saisie),divideattrapeZeroDivisionError(le calcul). - Une question ouverte :
dividedoit-elle renvoyer 0 en cas de division par zéro ? Mathématiquement, c’est une réponse fausse ! Dans bien des cas, il vaut mieux ne pas l’attraper ici, et laisser l’appelant décider. Nous en parlerons dans la leçon surraise.
as error — obtenir les détails de l’erreur
try:
number = int("12.5")
except ValueError as error:
print("Conversion failed:", error)Conversion failed: invalid literal for int() with base 10: '12.5'error est l’objet de l’erreur. L’afficher montre le message qui serait apparu à la dernière ligne du Traceback.
Erreur courante
Erreur 1 : un except vide qui attrape tout.
try:
age = int(input("Age: "))
print("Double:", age * 2)
reslt = age / 0
except:
print("Something went wrong")Il y a ici trois problèmes différents (la saisie, la division par zéro, et même une lettre manquante dans un nom de variable), et tous sont cachés sous « Something went wrong ». Impossible à déboguer. Et pire encore : un except: vide attrape aussi KeyboardInterrupt, donc même Ctrl + C n’arrêtera pas un programme coincé dans une boucle comme celle-ci !
La règle : n’attrapez que des erreurs précises que vous savez gérer. Toutes les autres continuent de remonter et font planter, et c’est une bonne chose, car c’est ainsi qu’on découvre les bugs.
Erreur 2 : un try énorme.
try:
# 40 lines of code...
except ValueError:
print("Invalid value")Laquelle des 40 lignes a échoué ? Impossible à savoir. Ne mettez dans le try que les lignes qui peuvent échouer avec l’erreur que vous attrapez, le moins possible.
Erreur 3 : « avaler » une erreur en silence.
try:
price = float(text)
except ValueError:
passpass signifie « ne rien faire ». L’erreur disparaît, et price n’existe peut-être même pas, ce qui provoquera une NameError déroutante quelques lignes plus loin. Faites toujours quelque chose : un message, une valeur par défaut expliquée, ou une écriture dans un journal.
Exercices
Les exercices n’utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d’abord seul, et n’ouvrez la solution qu’ensuite.
Exercice 1: Conversion sûre en nombre
Écrivez une fonction to_number(text) qui renvoie le texte sous forme de nombre entier, et si le texte n’est pas un nombre — renvoie None, sans que le programme plante.
print(to_number("42"))
print(to_number("hello"))
print(to_number("7"))Résultat attendu :
42
None
7Afficher la solution
def to_number(text):
try:
return int(text)
except ValueError:
return None
print(to_number("42"))
print(to_number("hello"))
print(to_number("7"))42
None
7int("hello") lance une ValueError. Sans try, le programme planterait. Avec except ValueError, nous décidons de ce qui se passe à la place.
Exercice 2: Une gestion différente pour chaque type d’erreur
Écrivez une fonction safe_divide(a_text, b_text) qui reçoit deux textes, les convertit en nombres et divise. Elle renvoie :
- le résultat de la division, si tout est valide ;
"numbers only"si l’un des textes n’est pas un nombre ;"cannot divide by zero"si le diviseur vaut 0.
print(safe_divide("10", "4"))
print(safe_divide("10", "0"))
print(safe_divide("ten", "2"))Résultat attendu :
2.5
cannot divide by zero
numbers onlyAfficher la solution
def safe_divide(a_text, b_text):
try:
return int(a_text) / int(b_text)
except ValueError:
return "numbers only"
except ZeroDivisionError:
return "cannot divide by zero"
print(safe_divide("10", "4"))
print(safe_divide("10", "0"))
print(safe_divide("ten", "2"))2.5
cannot divide by zero
numbers onlyChaque type d’erreur a son propre except, avec une gestion adaptée. Python vérifie les blocs except dans l’ordre et entre dans le premier qui correspond.
Exercice 3: Afficher le message d’erreur
Le code suivant échoue avec une ValueError. Enveloppez-le dans un try, attrapez l’erreur avec except ValueError as error, et affichez le texte Could not convert: suivi du message que Python a enregistré dans error :
number = int("12abc")Résultat attendu :
Could not convert: invalid literal for int() with base 10: '12abc'Afficher la solution
try:
number = int("12abc")
except ValueError as error:
print("Could not convert:", error)Could not convert: invalid literal for int() with base 10: '12abc'as error enregistre l’objet de l’erreur dans une variable. Quand on l’affiche, on obtient le message créé par Python.
Exercice 4: Choisir dans une liste
Vous avez une liste de noms. Écrivez une fonction pick(names, position_text) qui reçoit une position sous forme de texte (en commençant à 1) et renvoie le nom à cette position. Elle renvoie "not a number" pour une saisie qui n’est pas un nombre, et "no such position" pour une position qui n’existe pas.
names = ["Dana", "Avi", "Noa"]
print(pick(names, "2"))
print(pick(names, "abc"))
print(pick(names, "9"))Résultat attendu :
Avi
not a number
no such positionAfficher la solution
def pick(names, position_text):
try:
position = int(position_text)
return names[position - 1]
except ValueError:
return "not a number"
except IndexError:
return "no such position"
names = ["Dana", "Avi", "Noa"]
print(pick(names, "2"))
print(pick(names, "abc"))
print(pick(names, "9"))Avi
not a number
no such positionDeux erreurs différentes à deux endroits : int(...) peut lancer une ValueError, et l’accès à la liste peut lancer une IndexError.
Exercice 5: Un except qui attrape tout
def read_age(text):
try:
return int(text)
except:
return 0- Pourquoi un
except:sans type d’erreur est-il dangereux ? - Donnez un exemple de vrai bug que ce code cacherait.
- Corrigez le code.
Afficher la solution
- Un
except:vide attrape toutes les erreurs — y compris celles auxquelles nous n’avons pas pensé — et les cache. Le code continue comme si tout allait bien, avec une valeur fausse. - Si quelqu’un passe
Nonepar erreur au lieu d’un texte, uneTypeErrorest lancée — un vrai bug dans le code qui a appelé la fonction. Au lieu de le voir, on obtient silencieusement l’âge 0. (MêmeCtrl + Cest attrapé par unexcept:vide.) - N’attrapez que l’erreur attendue :
def read_age(text):
try:
return int(text)
except ValueError:
return 0
print(read_age("30"))
print(read_age("thirty"))30
0Résumé
- Exception = une erreur que Python lance à l’exécution. Si personne ne l’attrape, le programme plante.
try= « essaie ».except SomeError= « si cette erreur s’est produite, fais ceci au lieu de planter ».- Dès qu’une erreur est lancée, le reste du
tryest sauté. except SomeError as errordonne accès au message d’erreur.- N’attrapez que des erreurs précises, avec un
trypetit. Pas deexcept:vide ni depassqui avale les erreurs.