Gérer les erreurs¶
Lorsqu'on écrit un programme on peut déjà prévoir des problème possibles, par exemple si ma fonction moyenne récupère une liste vide alors son nombre d'éléments est 0 et la somme des valeurs divisée par 0 va poser un problème.
Pour gérer les cas problématiques on peut les tester avec if
mais que fait-on lorsqu'on repère une erreur ? On arrête le programme ? On corrige l'erreur ? On l'ignore ? Tout dépend où l'on repère l'erreur dans le programme.
Pensez par exemple à la fonction division qui récupère 0 au dénominateur. Que peut-elle faire ? Elle ne sait pas pourquoi on vient de faire une division. Dans le cas de la moyenne d'une liste de valeur, elle ne sait pas que 0 est la longueur de la liste vide.
La résolution d'une erreur est très dépendante du contexte mais souvent on n'a pas le contexte là où on détecte l'erreur, alors que faire ?
raise Exception¶
Python, comme d'autres langages, utilise la notion d'Exception
pour remonter un problème. Ainsi on indique à celui qui a appelé la fonction qu'on a détecté une erreur, à lui de se débrouiller.
def pas_un(x):
if x == 1:
raise Exception("On avait dit pas un !")
pas_un(1)
print("fin")
--------------------------------------------------------------------------- Exception Traceback (most recent call last) <ipython-input-2-9b9988560184> in <module> ----> 1 pas_un(1) 2 print("fin") <ipython-input-1-37b1697fdcae> in pas_un(x) 1 def pas_un(x): 2 if x == 1: ----> 3 raise Exception("On avait dit pas un !") Exception: On avait dit pas un !
On note que le print("fin")
n'a pas eu lieu car on a fait remonter une exception mais comme personne n'a géré l'exception, Python a décidé d'arrêter l'execution du programme.
Voici comment on gère les exceptions.
try ... except¶
On rattrape une erreur au niveau supérieur avec le mot clef except
qui suit un bloc try
:
try:
pas_un(1)
print("Tout va bien")
except:
print("Argh, une exception, on va l'ignorer")
print("fin")
Argh, une exception, on va l'ignorer fin
On a géré l'exception dans le bloc except
en affichant un message et c'est tout. On pourrait bien sûr faire d'autres choses comme corriger l'erreur, lancer une autre fonction qui interroge l'utilisateur, etc.
Pourquoi try et pas if ?¶
L'intérêt d'utiliser try
est de protéger toutes les instructions qui seront dans le bloc du try
y compris les appels à des fonctions que l'on n'a pas écrites. Si une erreur apparait là bas, elle sera soit gérée en interne et on ne la verra pas, ou bien elle sera remontée et à nous de la gérer.
Avec if
il faudrait pouvoir anticiper toutes les erreurs possibles ce qui devient rapidement impossible lorsqu'on appelle des fonctions compliquées ou écrites par d'autres.
Différentes exceptions¶
Lorsqu'on fait remonter une exception, il est utile d'indiquer le type d'exception afin que celui qui la gère puisse s'adapter : une erreur due à division par zéro n'est pas la même chose qu'une erreur due à un fichier manquant. Indiquer raise Exception
n'est pas suffisant, même si on écrit un commentaire utile, les commentaires n'étant pas interprétés par le langage.
Aussi on lève différents types d'exceptions.
2/0
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-4-e8326a161779> in <module> ----> 1 2/0 ZeroDivisionError: division by zero
open("fichier_inconnu")
--------------------------------------------------------------------------- FileNotFoundError Traceback (most recent call last) <ipython-input-5-1a622ad3e8e3> in <module> ----> 1 open("fichier_inconnu") FileNotFoundError: [Errno 2] No such file or directory: 'fichier_inconnu'
et voila l'exception ZeroDivisionError
et l'exception FileNotFoundError
. La liste des exceptions pré-définies
est sur https://docs.python.org/3/library/exceptions.html.
Il ne vous a pas échappé qu'il y a un message pour les humains qui accompagne les exceptions comme "division by zero" pour l'exception ZeroDivisionError ou "On avait dit pas un !" pour notre première Exception. Mais lorsqu'on programme on utilise le nom de l'exception pour l'intercepter :
try:
#res = 2/0 # changez la ligne commentée
res = 3 * res
except ZeroDivisionError:
print ("on a attrapé une division par 0, on fixe res à 1")
res = 1
except Exception as e:
print("on a attrapé une exception qui n'est pas une division par 0. C'est")
print(e)
print("On la fait remonter avec raise")
raise e
print(res)
del(res)
on a attrapé une exception qui n'est pas une division par 0. C'est name 'res' is not defined On la fait remonter avec raise
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-6-f95a48a7b932> in <module> 9 print(e) 10 print("On la fait remonter avec raise") ---> 11 raise e 12 print(res) 13 del(res) <ipython-input-6-f95a48a7b932> in <module> 1 try: 2 #res = 2/0 # changez la ligne commentée ----> 3 res = 3 * res 4 except ZeroDivisionError: 5 print ("on a attrapé une division par 0, on fixe res à 1") NameError: name 'res' is not defined
On gère différents types d'exception avec des blocs except
qui se suivent. Dès lors qu'un bloc est activé, tous les blocs suivants sont ignorés. Lorsqu'on ne spécifie pas le type de l'exception, alors on récupère toutes les exceptions.
En pratique on découvre à l'usage les exceptions que peuvent lever les bibliothèques que l'on utilise et ensuite on les prend en compte dans son code.
Finalement¶
Si on désire que des instructions soient excécutées quelque soit le type d'erreur, qu'elle soit gérée ou pas, alors
on introduit un bloc finally
qui sera toujours exécuté et qui peut permettre ainsi que mettre les choses en ordre avant l'explosion finale.
try:
fichier = open("data/notes")
"a" + 3
except FileNotFoundError:
print("Où est le fichier ?")
finally:
fichier.close()
print("Quoi qu'il arrive, le fichier a été fermé !")
Quoi qu'il arrive, le fichier a été fermé !
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-7-e5903f8cdabb> in <module> 1 try: 2 fichier = open("data/notes") ----> 3 "a" + 3 4 except FileNotFoundError: 5 print("Où est le fichier ?") TypeError: must be str, not int
What else?¶
Il existe un cas peu courant, donc optionnel pour un débutant mais si un jour vous vous trouvez bloqué, revenez ici, c'est peut-être la solution. On veut écrire du code (là où est g(x) ci-dessous)
- qui doit s'exécuter après le
try
d'un bloc (là ou est f(x) ci-dessous) - qui doit s'exécuter seulement s'il n'y a pas eu d'exeception lors du
try
- dont les exceptions ne doivent pas être interceptées par ce
try
Pour faire cela on utilise else
après le ou les except
:
try:
try:
f(x)
except:
e(x) # attrape les exceptions de f(x)
else:
g(x) # s'execute que si f(x) ne génère pas d'exception
except:
# attrape les exceptions de g(x) ou de e(x)
Notons que l'on peut avoir un finally
après le else
pour conclure le try
qu'il y ait eu des exceptions ou pas.
Plus loin¶
Il est possible de définir son type d'exception et ainsi de gérer de nouveaux cas. On verra comment faire cela lorsqu'on aura vu la notion d'objet.
{{ PreviousNext("04 - Les fonctions.ipynb", "06 - Premiers programmes.ipynb") }}