Les exceptions

    Publicités

Users Who Are Viewing This Thread (Total: 0, Members: 0, Guests: 0)

Superman

V
Ancien staff
Dec 2, 2009
2,489
0
596
À quoi ça sert ?

Nous avons déjà été confrontés à des erreurs dans nos programmes ; certaines que j'ai volontairement provoquées, mais la plupart que vous avez dû rencontrer si vous avez testé un minimum des instructions dans l'interpréteur.
smile.png
Quand Python rencontre une erreur dans votre code, il lève une exception. Sans le savoir, vous avez donc déjà vu des exceptions levées par Python :

Code : Python Console1
2
3
4
5
>>> # exemple classique : test d'une division par zéro
>>> variable = 1/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: int division or modulo by zero




Attardons-nous sur la dernière ligne. Nous y trouvons deux informations :


  • ZeroDivisionError : le type de l'exception
  • int division or modulo by zero : le message qu'envoie Python pour vous aider à comprendre l'erreur qui vient de se produire.


Python lève donc des exceptions quand il trouve une erreur, soit dans le code (une erreur de syntaxe par exemple), soit dans l'opération que vous lui demandez de faire.

Notez qu'à l'instar des variables, on trouve des types d'exceptions que Python va utiliser dans plusieurs situations. Le type d'exceptions ValueError notamment sera levé par Python dans plusieurs situations différentes incluant des erreurs de "valeurs". C'est donc dans ce cas le message qui vous indique plus clairement le problème. Nous verrons dans la prochaine partie consacrée à la Programmation Orientée Objet ce que sont réellement ces types d'exceptions.
smile.png


Bon, c'est très joli d'avoir cette exception. On voit le fichier et la ligne à laquelle s'est produite l'erreur (très pratique quand on commence à travailler sur un projet) et on a une indication sur le problème qui suffit en général à le régler. Mais Python permet quelque chose de bien plus pratique.
clin.png


Admettons que certaines erreurs puissent être provoquées par l'utilisateur. Par exemple, on demande à l'utilisateur d'entrer, au clavier, un entier, et il entre une chaîne de caractères... problème. Nous avons déjà rencontré cette situation, souvenez-vous du programme bissextile.

Code : Python1
2
annee = input() # on demande à l'utilisateur d'entrer l'année
annee = int(annee) # on essaye de convertir l'année en un entier




Je vous avais dit que si l'utilisateur rentrait ici quelque chose d'inconvertible en entier (une lettre par exemple), le programme planterait. En fait, il lève une exception, et Python arrête l'exécution du programme. Si vous testez le programme directement dans l'explorateur, il va se fermer tout de suite (en fait, il affiche bel et bien l'erreur mais se referme aussitôt).
heureux.png


Dans ce cas, et dans d'autres similaires, Python permet de tester un bout de code. S'il ne renvoie aucune erreur, Python continue. Sinon, on peut lui demander d'exécuter une autre action (par exemple, redemander à l'utilisateur d'entrer l'année). C'est ce que nous allons voir ici.
smile.png

Forme minimale du bloc try

On va parler ici de bloc try. Nous allons en effet mettre les instructions que nous souhaitons tester dans un premier bloc d'instructions et les instructions à exécuter en cas d'erreur dans un autre bloc d'instructions. Sans plus attendre, voici la syntaxe :

Code : Python1
2
3
4
try:
# bloc à essayer
except:
# bloc qui sera exécuté en cas d'erreur




Dans l'ordre, nous trouvons :


  • Le mot-clé try suivi des deux points ":" (try signifie "essayer" en anglais)
  • Le bloc d'instructions à essayer
  • Le mot-clé except suivi, une fois encore, des deux points ":". Il se trouve au même niveau d'indentation que le try
  • Le bloc d'instructions qui sera exécuté si une erreur est trouvée dans le premier bloc.


Reprenons notre test de conversion en enfermant l'instruction qui pourrait lever une exception dans un bloc try.

Code : Python1
2
3
4
5
annee = input()
try: # on essaye de convertir l'année en entier
annee = int(annee)
except:
print("Erreur lors de la conversion de l'année.")




Vous pouvez tester ce code en précisant plusieurs valeurs différentes à la variable annee comme "2010" ou "annee2010".

J'ai parlé de forme minimale et ce n'est pas pour rien.
heureux.png
D'abord il va de soi que vous ne pouvez intégrer cette solution directement dans votre code. En effet, si l'utilisateur entre une année inconvertible, le système affiche certes une erreur mais finit par planter (puisque l'année, au final, n'a pas été convertie). Une des solutions envisageable est d'attribuer une valeur par défaut à l'année en cas d'erreur, ou de redemander à l'utilisateur d'entrer l'année.

Ensuite et surtout, cette méthode est assez grossière. Elle essaye une instruction et intercepte n'importe quelle exception liée à cette instruction. Ici, c'est acceptable car nous n'avons pas énormément d'erreurs possibles sur cette instruction. Mais c'est une mauvaise habitude à prendre. Voici une manière plus élégante et moins dangereuse.
clin.png

Forme plus complète

Nous allons apprendre à compléter notre bloc try. Comme je l'ai indiqué plus haut, la forme minimale est à éviter pour plusieurs raisons.

D'abord, elle ne différencie pas les exceptions qui pourront être levées dans le bloc try. Ensuite, Python peut lever des exceptions qui ne signifient pas nécessairement qu'il y a eu une erreur.

Exécuter le bloc except pour un type d'exceptions précis


Dans l'exemple que nous avons vu plus haut, on ne pense qu'à un type d'exceptions qui pourrait être levé : le type ValueError qui pourrait trahir une erreur de conversion. Voyons un autre exemple :

Code : Python1
2
3
4
try:
resultat = numerateur / denominateur
except:
print("Une erreur est survenue... laquelle ?")




Ici, on peut trouver plusieurs erreurs possibles, levant chacune une exception différente.


  • NameError : l'une des variables numerateur ou denominateur n'a pas été définie (elle n'existe pas). Si vous essayez dans l'interpréteur l'instruction print(numerateur) alors que vous n'avez pas défini la variable numerateur, vous aurez la même erreur
  • TypeError : l'une des variables numerateur ou denominateur ne peut diviser ou être divisée (les chaînes de caractères ne peuvent être divisées, ni diviser d'autres types, par exemple). Cette exception est levée car vous utilisez l'opérateur de division "/" sur des types qui ne savent pas quoi en faire
  • ZeroDivisionError : encore elle ! Si denominateur est égale à 0, cette exception sera levée.


Cette énumération n'est pas une liste exhaustive de toutes les exceptions qui peuvent être levées à l'exécution de ce code. Elle est surtout là pour vous montrer que plusieurs erreurs peuvent se produire sur une instruction (c'est encore plus flagrant sur un bloc constitué de plusieurs instructions) et que la forme minimale intercepte toutes ces erreurs sans les distinguer, ce qui peut être problématique parfois.
langue.png


Tout se joue sur la ligne du except. Entre ce mot-clé et les deux points, vous pouvez préciser le type de l'exception que vous souhaitez traiter.

Code : Python1
2
3
4
try:
resultat = numerateur / denominateur
except NameError:
print("La variable numerateur ou denominateur n'a pas été définie.")




Ce code ne traite que le cas où une exception NameError est levée. On peut intercepter les autres types d'exceptions en faisant d'autres blocs except à la suite :

Code : Python1
2
3
4
5
6
7
8
try:
resultat = numerateur / denominateur
except NameError:
print("La variable numerateur ou denominateur n'a pas été définie.")
except TypeError:
print("La variable numerateur ou denominateur possède un type incompatible avec la division.")
except ZeroDivisionError:
print("La variable denominateur est égale à 0.")




C'est mieux non ?
smile.png


Allez un petit dernier... euh... pour l'instant
diable.png
.

On peut capturer l'exception et afficher son message grâce au mot-clé as que vous avez déjà vu, dans un autre contexte (si si, rappelez-vous de l'importation de modules
clin.png
).

Code : Python1
2
3
4
try:
# bloc de test
except type_de_l_exception as exception_retournee:
print("Voici l'erreur :", exception_retournee)




Dans ce cas, une variable exception_retournee est créée par Python si une exception du type précisé est levée dans le bloc try.

Je vous conseille de toujours préciser un type d'exceptions après except (sans nécessairement capturer l'exception dans une variable, bien entendu). D'abord, vous ne devez pas utiliser try comme une méthode miracle pour tester n'importe quel bout de code. Il est important que vous gardiez le maximum de contrôle sur votre code. Cela signifie que si une erreur se produit, vous devez être capable de l'anticiper. En pratique, vous n'irez pas jusqu'à tester si une variable quelconque existe bel et bien,
langue.png
il faut faire un minimum confiance à son code. Mais si vous êtes en face d'une division et que le dénominateur pourrait avoir une valeur de 0, mettez la division dans un bloc try et précisez après le except le type de l'exception qui risque de se produire (ZeroDivisionError dans cet exemple).

Si vous adoptez la forme minimale (à savoir except sans préciser un type d'exceptions qui pourrait se produire sur le bloc try), toutes les exceptions seront traitées de la même façon. Et même si "exception = erreur" la plupart du temps, ce n'est pas toujours le cas. Par exemple, Python lève une exception quand vous voulez fermer votre programme avec le raccourci CTRL + C. Ici vous ne voyez peut-être pas le problème, mais si votre bloc try est dans une boucle par exemple, vous ne pourrez pas arrêter votre programme avec CTRL + C, puisque l'exception sera traitée par votre except.

Je vous conseille donc de toujours préciser un type d'exceptions possible après votre except. Vous pouvez bien entendu tester dans l'interpréteur de commandes Python pour reproduire l'exception que vous voulez traiter et ainsi connaitre son type.

Les mots-clés else et finally


Ce sont deux mots-clés qui vont nous permettre de construire un bloc try plus complet.

else


Vous avez déjà vu ce mot-clé et j'espère que vous vous en rappelez.
smile.png
Dans un bloc try, else va permettre d'exécuter une action si aucune erreur n'est trouvée dans le bloc. Voici un petit exemple :

Code : Python 1
2
3
4
5
6
7
8
9
10
try:
resultat = numerateur / denominateur
except NameError :
print("La variable numerateur ou denominateur n'a pas été définie.")
except TypeError:
print("La variable numerateur ou denominateur possède un type incompatible avec la division.")
except ZeroDivisionError:
print("La variable denominateur est égale à 0.")
else:
print("Le résultat obtenu est", resultat)




Dans les faits, on utilise assez peu else. La plupart des codeurs préfère mettre la ligne contenant le print directement dans le bloc try. Pour ma part, je trouve que c'est important de distinguer entre le bloc try et ce qui s'effectue ensuite. La ligne du print ne produira vraisemblablement aucune erreur, inutile de la mettre dans le bloc try.

finally


finally permet d'exécuter du code après un bloc try, quelle que soit l'exécution du dit bloc. La syntaxe est des plus simples :

Code : Python1
2
3
4
5
6
try:
# test d'instruction(s)
except TypeDInstruction:
# traitement en cas d'erreur
finally:
# instruction(s) exécutée(s) qu'il y ait eu erreurs ou non




Est-ce que ça ne revient pas au même si on met du code juste après le bloc ?


Pas tout à fait. Le bloc finally est exécuté dans tous les cas de figure. Quand bien même Python trouverait une instruction return dans votre bloc except par exemple, il exécutera le bloc finally.

Un petit bonus : le mot-clé pass


Il peut arriver, dans certains cas, que l'on souhaite tester un bloc d'instructions... mais ne rien faire en cas d'erreur. Toutefois, un bloc try ne peut être seul.

Code : Python Console1
2
3
4
5
6
7
>>> try:
... 1/0
...
File "<stdin>", line 3

^
SyntaxError: invalid syntax




Il existe un petit mot-clé que l'on peut utiliser dans ce cas. Son nom est pass et sa syntaxe est très simple d'utilisation :

Code : Python1
2
3
4
try:
# test d'instruction(s)
except type_de_l_exception: # rien ne doit se passer en cas d'erreur
pass




Je ne vous encourage pas particulièrement à utiliser ce mot-clé mais il existe et vous le savez à présent
clin.png
.

pass n'est pas un mot-clé propre aux exceptions : on peut également le trouver dans des conditions, ou dans des fonctions que l'on souhaite laisser vide.

Voilà, nous avons vu l'essentiel. Il nous reste à voir comment lever une exception (ce sera très rapide) et on pourra passer au QCM.
Lever une exception

Hmmm... je vois d'ici les mines sceptiques (non non, ne vous cachez pas !)
smile.png
. Vous vous demandez probablement pourquoi vous feriez le boulot de Python en levant des exceptions. Après tout, votre boulot, c'est en théorie d'éviter que votre programme plante.
heureux.png


Parfois cependant, il pourra être utile de lever des exceptions. Vous verrez tout l'intérêt du concept quand vous créerez vos propres classes... mais ce n'est pas pour tout de suite
clin.png
. En attendant, je vais vous donner la syntaxe et vous pourrez faire quelques tests, vous verrez de toute façon qu'il n'y a rien de compliqué.

On utilisera un nouveau mot-clé pour lever une exception... le mot-clé raise.

Code : Python1
raise TypeDeLException("message à afficher")




Prenons un petit exemple, toujours autour de notre programme bissextile. Nous allons lever une exception de type ValueError si l'utilisateur entre une année négative ou nulle.

Code : Python1
2
3
4
5
6
7
annee = input() # l'utilisateur entre l'année
try:
annee = int(annee) # on tente de convertir l'année
if annee<=0:
raise ValueError("l'année entrée est négative ou nulle")
except ValueError:
print("La valeur entrée est invalide (l'année est peut-être négative).")




Ce que nous venons de faire, nous aurions pu le faire sans utiliser d'exceptions, mais c'était surtout pour vous montrer la syntaxe dans un véritable contexte. Ici, on lève une exception que l'on intercepte immédiatement ou presque, l'intérêt est donc limité, mais bien entendu la plupart du temps ce n'est pas le cas.

Il reste des choses à découvrir sur les exceptions, mais on en a assez fait pour ce chapitre et cette partie.
smile.png
Je ne vous demande pas de connaitre toutes les exceptions que Python est amené à utiliser (certaines d'entre elles pourront d'ailleurs n'exister que dans certains modules). En revanche, vous devez être capable de savoir, grâce à l'interpréteur de commandes, quelles exceptions peuvent être levées par Python dans une certaine situation.


Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!