Les exceptions et la gestion des erreurs¶
Toutes les erreurs qui se produisent lors de l’exécution d’un programme Python sont représentées par une exception. Une exception est un objet qui contient des informations sur le contexte de l’erreur. Lorsqu’une exception survient et qu’elle n’est pas traitée alors elle produit une interruption du programme et elle affiche sur la sortie standard un message ainsi que la pile des appels (stacktrace). La pile des appels présente dans l’ordre la liste des fonctions et des méthodes qui étaient en cours d’appel au moment où exception est survenue.
Si nous prenons le programme suivant :
programme.py
¶1 2 3 4 5 6 7 | def do_something_wrong():
1 / 0
def do_something():
do_something_wrong()
do_something()
|
L’exécution de ce programme affiche sur la sortie d’erreur
$ python3 programme.py
Traceback (most recent call last):
File "test.py", line 7, in <module>
do_something()
File "test.py", line 5, in do_something
do_something_wrong()
File "test.py", line 2, in do_something_wrong
1 / 0
ZeroDivisionError: division by zero
Nous voyons que le programme a échoué à cause d’une exception de type
ZeroDivisionError
et nous avons la pile d’erreur qui nous indique que le
programme s’est interrompu à la ligne 2 suite à l’instruction
1 / 0
dans la fonction do_something_wrong()
qui a été appelée par la
fonction do_something()
à la ligne 5, elle-même appelée par le programme
à la ligne 7.
Traiter une exception¶
Parfois un programme est capable de traiter le problème à l’origine de l’exception. Par exemple si le programme demande à l’utilisateur de saisir un nombre et que l’utilisateur saisit une valeur erronée, le programme peut simplement demander à l’utilisateur de saisir une autre valeur. Plutôt que de faire échouer le programme, il est possible d’essayer de réaliser un traitement et, s’il échoue de proposer un traitement adapté :
1 2 3 4 5 | nombre = input("Entrez un nombre : ")
try:
nombre = int(nombre)
except ValueError:
print("Désolé la valeur saisie n'est pas un nombre.")
|
À la ligne 2, on démarre un bloc try
pour indiquer le traitement
que l’on désire réaliser. Si ce traitement est interrompu par une exception,
alors l’interpréteur recherche un bloc except
correspondant
au type (ou à un type parent) de l’exception. Dans notre exemple, si la fonction
int
ne peut pas créer un entier à partir du paramètre, elle produit
une exception de type ValueError
. Donc après le bloc try
,
on ajoute une instruction except
pour le type ValueError
(lignes 4 et 5). Ce bloc n’est exécuté que si la conversion en entier n’est pas
possible.
Lorsqu’une exception survient dans un bloc try
, elle interrompt
immédiatement l’exécution du bloc et l’interpréteur recherche un bloc
except
pouvant traiter l’exception. Il est possible d’ajouter
plusieurs blocs except
à la suite d’un bloc try
.
Chacun d’entre-eux permet de coder un traitement particulier pour chaque
type d’erreur :
1 2 3 4 5 6 7 8 9 | try:
numerateur = int(input("Entrez un numérateur : "))
denominateur = int(input("Entrez un dénominateur : "))
resultat = numerateur / denominateur
print("Le resultat de la division est", resultat)
except ValueError:
print("Désolé, les valeurs saisies ne sont pas des nombres.")
except ZeroDivisionError:
print("Désolé, la division par zéro n'est pas permise.")
|
L’intérêt de la structure du try except
est qu’elle permet de
dissocier dans le code la partie du traitement normal de la partie du traitement
des erreurs.
Note
Si le même traitement est applicable pour des exceptions de types différents,
il est possible de fournir un seul bloc except
avec le tuple
des exceptions concernées :
try:
numerateur = int(input("Entrez un numérateur : "))
denominateur = int(input("Entrez un dénominateur : "))
resultat = numerateur / denominateur
print("Le resultat de la division est", resultat)
except (ValueError, ZeroDivisionError):
print("Désolé, quelque chose ne s'est pas bien passé.")
Récupérer le message d’une exception¶
Les exceptions possèdent une représentation sous forme de chaîne de caractères pour fournir un message. Pour avoir accès à l’exception, on utilise la syntaxe suivante :
1 2 3 4 5 | nombre = input("Entrez nombre : ")
try:
nombre = int(nombre)
except ValueError as e:
print(e)
|
À la ligne 4, on précise que l’exception de type ValueError
est accessible
dans le bloc except
sous le nom e
ce qui permet d’afficher
l’exception (c’est-à-dire le message d’erreur).
Clause else¶
Il est possible d’ajouter une clause else
après les blocs try except
.
Le bloc else
est exécuté uniquement si le bloc try
se termine
normalement, c’est-à-dire sans qu’une exception ne survienne.
1 2 3 4 5 6 7 8 | try:
numerateur = int(input("Entrez un numérateur : "))
denominateur = int(input("Entrez un dénominateur : "))
resultat = numerateur / denominateur
except (ValueError, ZeroDivisionError):
print("Désolé, quelque chose ne s'est pas bien passé.")
else:
print("Le resultat de la division est", resultat)
|
Note
Le bloc else
permet de distinguer la partie du code qui est susceptible
de produire une exception de celle qui fait partie du comportement nominal
du code mais qui ne produit pas d’exception.
Post-traitement¶
Dans certain cas, on souhaite réaliser un traitement après le bloc try
que ce dernier se termine correctement ou bien qu’une exception soit survenue.
Dans cas, on place le code dans un bloc finally
.
1 2 3 4 5 6 7 8 9 | try:
numerateur = int(input("Entrez un numérateur : "))
denominateur = int(input("Entrez un dénominateur : "))
resultat = numerateur / denominateur
print("Le resultat de la division est", resultat)
except (ValueError, ZeroDivisionError):
print("Désolé, quelque chose ne s'est pas bien passé.")
finally:
print("afficher ceci quel que soit le résultat")
|
Un bloc finally
est systématique appelé même si le bloc
try
est interrompu par une instruction return
.
Lever une exception¶
Il est possible de signaler une exception grâce au mot-clé raise
.
if x < 0:
raise ValueError
Pour la plupart des exceptions, il est possible de passer en paramètre un message pour décrire le cas exceptionnel :
if x < 0:
raise ValueError("La valeur ne doit pas être négative")
Le mot-clé raise
est également utilisé pour relancer une exception
dans un bloc mot-clé except
.
1 2 3 4 5 6 7 8 | try:
numerateur = int(input("Entrez un numérateur : "))
denominateur = int(input("Entrez un dénominateur : "))
resultat = numerateur / denominateur
print("Le resultat de la division est", resultat)
except (ValueError, ZeroDivisionError):
print("Désolé, quelque chose ne s'est pas bien passé.")
raise
|
Dans l’exemple ci-dessus, l’exception traitée dans le bloc except
est relancée à la ligne 8.
Note
Il est également possible de créer une nouvelle exception à partir d’une exception existante. Dans ce cas, la nouvelle exception aura comme cause l’exception d’origine
1 2 3 4 5 6 7 8 | try:
numerateur = int(input("Entrez un numérateur : "))
denominateur = int(input("Entrez un dénominateur : "))
resultat = numerateur / denominateur
print("Le resultat de la division est", resultat)
except (ValueError, ZeroDivisionError) as e:
print("Désolé, quelque chose ne s'est pas bien passé.")
raise Exception from e
|
Les exceptions à connaître¶
Plusieurs exceptions décrivent des erreurs très courantes. Il est intéressant de bien les connaître pour écrire des programmes capables de traiter les erreurs éventuelles :
ValueError
Signale que la valeur n’est pas correcte.
>>> int('a') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: '
Dans l’exemple ci-dessus, la valeur de la chaîne de caractères ne représente pas un nombre, sa conversion en entier va donc échouer.
TypeError
Signale que le type de la donnée n’est pas correct pour l’instruction à exécuter.
>>> 1 + "a" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str'
Dans l’exemple ci-dessus, une chaîne de caractères ne peut pas être à droite de l’opérateur
+
si un nombre est à gauche. En effet, dans ce cas, le signe+
représente l’opération arithmétique de l’addition.IndexError
Signale que l’on veut accéder à un élément d’une liste, d’un n-uplet ou d’une chaîne de caractères avec un index invalide.
>>> "hello"[100] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range
KeyError
Signale que la clé n’existe pas dans un dictionnaire.
>>> d = {} >>> d['une cle'] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'une cle'
Exercices¶
Exercice : Contrôle de la saisie d’un nombre
Écrivez un programme qui demande à l’utilisateur de saisir un nombre. Si l’utilisateur ne saisit pas un nombre, le programme doit indiquer à l’utilisateur qu’il a fait une erreur et lui redemander de saisir un nombre.
Puis le programme affiche le nombre saisi.
Exercice : Fonction controlant le type des paramètres
Écrivez la fonction dire_bonjour_a(nom)
qui affiche sur la console
« Bonjour » suivi du nom passé en paramètre. Complétez la fonction ci-dessous
pour que l’appel échoue avec une exception si le paramètre nom
n’est
pas une chaîne de caractères ou si la chaîne de caractères est vide :
def dire_bonjour_a(nom):
"""Cette fonction doit produire une exception lorsque
nom n'est pas une chaîne de caractères ou bien si nom
correspond à la chaîne vide."""
# TODO
print("Bonjour", a)
Exercice : QCM en invite de commande (suite)
Reprenez l’exercice sur le QCM du chapitre précédent.
Utilisez le mécanisme des exceptions pour :
vérifier que l’utilisateur saisit bien un nombre pour indiquer son choix. Si ce n’est pas le cas, il faut afficher à nouveau les choix de réponse et lui redemander son choix.
vérifier que l’utilisateur saisit bien un numéro qui correspond à un choix. Sinon, il faut lui indiquer que ce choix n’existe pas et lui redemander son choix.