Exceptions B : else et finally — la structure complète
- Expliquer quand s’exécute chacun des quatre blocs — try, except, else, finally
- Utiliser
elsepour du code qui ne s’exécute que s’il n’y a pas eu d’erreur - Utiliser
finallypour les actions de nettoyage qui doivent toujours avoir lieu - Prédire l’ordre des affichages dans un programme avec un try complet
Qu’allons-nous apprendre ?
Dans la leçon précédente, nous avons utilisé try et except. La structure comporte deux autres parties :
else— s’exécute seulement s’il n’y a pas eu d’erreur.finally— s’exécute toujours, avec ou sans erreur.
Pourquoi en avons-nous besoin ?
Imaginez un code qui ouvre une connexion à une base de données (les thèmes backend et bases de données), effectue une action et ferme la connexion. Que se passe-t-il si l’action échoue au milieu ? Si la fermeture est écrite après l’action, elle ne sera pas exécutée, et la connexion reste ouverte. Après une centaine d’échecs de ce genre, le serveur plantera faute de connexions disponibles.
finally garantit que le nettoyage a lieu dans tous les cas. else garde le try petit, et rend clair ce qui ne s’exécute qu’après une réussite.
Explication à partir des bases
Les quatre blocs
| Bloc | Quand il s’exécute | À quoi il sert |
|---|---|---|
try | On commence toujours par lui | Le code qui peut échouer |
except | Seulement si une erreur du type correspondant a été lancée | Gérer l’erreur |
else | Seulement si aucune erreur n’a été lancée dans le try | La suite qui dépend de la réussite |
finally | Toujours, à la fin, dans tous les cas | Le nettoyage : fermer des fichiers, des connexions, un message de fin |
try = vous essayez de faire un gâteau. except = s’il a brûlé, vous en commandez un à la boulangerie. else = s’il est réussi, vous le décorez (on ne décore pas un gâteau brûlé !). finally = dans tous les cas, vous éteignez le four et faites la vaisselle.
Pourquoi else et pas simplement continuer dans le try ?
On peut mettre le code de la suite à l’intérieur du try. Mais alors, si ce code échoue avec la même erreur, le except l’attrapera par erreur, et traitera le mauvais problème. else rend les choses claires : seulement la ligne risquée dans le try, et le reste à l’extérieur.
finally s’exécute même quand…
- Il y a eu une erreur et elle a été attrapée.
- Il y a eu une erreur et elle n’a pas été attrapée.
finallys’exécute, puis le programme plante. - Il y a un
returnau milieu dutry.finallys’exécute avant que la fonction ne renvoie vraiment.
Syntaxe
try:
# risky code
except SomeError:
# runs only if SomeError happened
else:
# runs only if NO error happened in try
finally:
# runs ALWAYS, lastOrdre obligatoire : try → except (un ou plusieurs) → else (facultatif) → finally (facultatif). On peut aussi avoir try + finally sans except.
Premier exemple de code
week-2/full_try.py :
def convert(text):
print("--- converting:", text)
try:
number = int(text)
except ValueError:
print("except: not a number")
else:
print("else: success, doubled =", number * 2)
finally:
print("finally: done with", text)
convert("21")
convert("hi")Sortie :
--- converting: 21
else: success, doubled = 42
finally: done with 21
--- converting: hi
except: not a number
finally: done with hiAnalyse du code ligne par ligne
| Bloc | convert("21") | convert("hi") |
|---|---|---|
try: number = int(text) | Réussit : number = 21 | ValueError |
except ValueError: | Sauté | S’exécute |
else: | S’exécute : affiche 42 | Sauté |
finally: | S’exécute | S’exécute |
else et except ne s’exécutent jamais tous les deux : soit il y a eu une erreur, soit il n’y en a pas eu. finally, en revanche, s’exécute dans les deux cas.
Un autre exemple
Une « connexion » qui doit être fermée. Ici, nous simulons une connexion à une ressource externe (comme une base de données), et voyons que finally la ferme aussi bien quand quelque chose échoue au milieu que lorsqu’il y a un return. week-2/connection_demo.py :
def open_connection():
print("[connection opened]")
def close_connection():
print("[connection closed]")
def fetch_price(product_name, prices):
open_connection()
try:
price = prices[product_name]
except KeyError:
print("No such product:", product_name)
return None
else:
print("Found", product_name)
return price
finally:
close_connection()
catalog = {"bread": 8, "milk": 6}
print("Result:", fetch_price("milk", catalog))
print()
print("Result:", fetch_price("cake", catalog))Sortie :
[connection opened]
Found milk
[connection closed]
Result: 6
[connection opened]
No such product: cake
[connection closed]
Result: NoneAnalyse :
catalog = {"bread": 8, "milk": 6}— c’est un dict (dictionnaire) : chaque clé ("bread") est associée à une valeur (8).prices["milk"]renvoie 6. Nous l’apprendrons en détail dans le thème structures de données et fonctions. Pour l’instant, il suffit de savoir qu’accéder à une clé qui n’existe pas lance uneKeyError.- Remarquez : il y a un
returnà la fois dansexceptet danselse. Et pourtant[connection closed]est affiché avant leResult. Autrement dit,finallys’exécute après que lereturna été « décidé », mais avant que la fonction ne renvoie vraiment. - Sans
finally, il faudrait écrireclose_connection()avant chaquereturn. Facile à oublier, et beaucoup de duplication.
Erreur courante
Erreur 1 : mettre le nettoyage après le try/except au lieu de dans finally.
open_connection()
try:
price = prices[name]
except KeyError:
print("missing")
return None
close_connection() # never reached when except returns!En cas de KeyError, le return quitte la fonction, et close_connection() ne s’exécute pas. La connexion reste ouverte. finally résout exactement ce problème.
Erreur 2 : le code de la suite dans le try, et un except qui attrape la mauvaise erreur.
try:
age = int(text)
category = ["child", "adult"][age // 18] # can also raise IndexError... or ValueError?
except ValueError:
print("Age must be a number")Si la ligne de la suite échoue, par exemple avec une IndexError pour l’âge 40 (40 // 18 = 2, et il n’y a pas d’indice 2), le message n’a aucun rapport. Ne mettez que int(text) dans le try, et la suite dans else.
Erreur 3 : croire que finally « attrape » l’erreur. Ce n’est pas le cas. S’il n’y a pas de except correspondant, finally s’exécute, et ensuite le programme plante avec un Traceback.
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: Prédire la sortie
Écrivez ce qui sera affiché pour chaque appel — avant de l’exécuter :
def half(text):
try:
number = int(text)
except ValueError:
print("not a number:", text)
else:
print("half is", number / 2)
finally:
print("checked", text)
half("8")
half("x")Afficher la solution
def half(text):
try:
number = int(text)
except ValueError:
print("not a number:", text)
else:
print("half is", number / 2)
finally:
print("checked", text)
half("8")
half("x")half is 4.0
checked 8
not a number: x
checked x- Pour
"8", il n’y a pas d’erreur, doncelses’exécute, suivi definally. - Pour
"x", uneValueErrorest lancée, doncexcepts’exécute — etelsene s’exécute pas.finallys’exécute dans les deux cas.
Exercice 2: finally s’exécute même après return
def check(number):
try:
if number > 0:
return "positive"
return "not positive"
finally:
print("done checking", number)
print(check(5))
print(check(-2))Qu’est-ce qui sera affiché, et dans quel ordre ? Expliquez pourquoi.
Afficher la solution
def check(number):
try:
if number > 0:
return "positive"
return "not positive"
finally:
print("done checking", number)
print(check(5))
print(check(-2))done checking 5
positive
done checking -2
not positivefinally s’exécute avant que la fonction ne renvoie vraiment — même quand il y a un return dans le try. C’est pourquoi « done checking » est affiché avant la valeur renvoyée par la fonction.
Exercice 3: Les quatre blocs
Écrivez une fonction parse_quantity(text) qui utilise les quatre blocs :
try— convertit le texte en nombre ;except— afficheinvalid: <text>;else— affichequantity: <number>;finally— affiche---.
parse_quantity("3")
parse_quantity("three")Résultat attendu :
quantity: 3
---
invalid: three
---Afficher la solution
def parse_quantity(text):
try:
quantity = int(text)
except ValueError:
print("invalid:", text)
else:
print("quantity:", quantity)
finally:
print("---")
parse_quantity("3")
parse_quantity("three")quantity: 3
---
invalid: three
---Le code qui ne s’exécute que lorsque tout a réussi est dans else et non dans le try — ainsi le try reste petit, et n’attrape que la ligne qui peut vraiment échouer.
Exercice 4: Fermer ce que l’on a ouvert
Les fonctions open_register() et close_register() ouvrent et ferment une caisse enregistreuse. Écrivez une fonction sell(prices, position) qui ouvre la caisse, renvoie le prix à la position choisie (la position est un indice de la liste — elle commence à 0), et ferme toujours la caisse — même quand la position n’existe pas. Une position qui n’existe pas renvoie None.
def open_register():
print("register opened")
def close_register():
print("register closed")
print(sell([10, 20, 30], 1))
print(sell([10, 20, 30], 7))Résultat attendu :
register opened
register closed
20
register opened
register closed
NoneAfficher la solution
def open_register():
print("register opened")
def close_register():
print("register closed")
def sell(prices, position):
open_register()
try:
return prices[position]
except IndexError:
return None
finally:
close_register()
print(sell([10, 20, 30], 1))
print(sell([10, 20, 30], 7))register opened
register closed
20
register opened
register closed
NoneC’est exactement le rôle de finally : le nettoyage — fermer une ressource qui a été ouverte, quelle que soit la façon dont la fonction s’est terminée.
Exercice 5: else ou dans le try ?
Deux développeurs ont écrit le même code de deux façons :
# A # B
try: try:
number = int(text) number = int(text)
print(100 / number) except ValueError:
except ValueError: print("not a number")
print("not a number") else:
print(100 / number)Quelle est la différence, et quelle version est la meilleure ? Que se passera-t-il dans chacune quand text vaut "0" ?
Afficher la solution
- Dans A, la division est aussi dans le
try. Dans B, seule la conversion est dans letry, et la division est passée danselse. - Quand
textvaut"0", les deux versions lancent uneZeroDivisionError, qu’aucunexceptn’attrape, et le programme plante. Mais dans B, il est tout à fait clair que leexcept ValueErrorne concerne que la conversion. - B est meilleure : un
tryaussi petit que possible. Ainsi,exceptn’attrape pas par erreur des erreurs venant d’autres lignes, et il est facile de voir ce que fait chaque partie.
Résumé
try— le code risqué.except— seulement si une erreur correspondante s’est produite.else— seulement s’il n’y a pas eu d’erreur.finally— toujours.exceptetelsene s’exécutent jamais ensemble.finallys’exécute même après unreturn, et aussi quand l’erreur n’a pas été attrapée (et le programme plante ensuite).elsegarde letrypetit et ciblé.finallyest l’endroit du nettoyage : fermer les fichiers et les connexions.