On a vu jusqu’à présent différentes situations où une opération provoquait le plantage de python et interrompait l’exécution.
>>> x = 1 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> ls = [1, 2]
>>> ls[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
ATTENTION ceci n’est pas tellement gênant dans une session interactive. Mais lors de l’exécution d’un script prenant plusieurs heures, cela entraine la perte de toutes les données non sauvegardées sur disque!
REMARQUE le type d’erreur générée est différent dans les trois cas ci-dessus.
On va s’intéresser dans cette leçon à la façon d’intercepter ces erreurs (plus précisément ces exceptions) afin de pouvoir continuer l’exécution. Il faudra utiliser ce mécanisme avant d’effectuer une opération
On montrera ensuite comment générer soi-même des exceptions.
On va intercepter les exceptions en utilisant l’instruction composée try
.
Commençons pas montrer l’utilisation sur les exemples introduits ci-dessus.
>>> try:
... x = 1 / 0
... except ZeroDivisionError:
... print("Diviser par zéro c'est mal!")
... x = float("NaN")
...
Diviser par zéro c'est mal!
>>> x
nan
>>> try:
... y
... except NameError:
... print("Pas de variable y!")
...
Pas de variable y!
>>> ls = [1, 2]
>>> try:
... ls[3]
... except IndexError:
... print("Pas d'élément d'indice 3")
...
Pas d'élément d'indice 3
La syntaxe générale de cette instruction est la suivante:
try:
INSTRUCTION_1
...
INSTRUCTION_N
except Erreur_1:
INSTRUCTION_1_1
...
INSTRUCTION_1_N1
except Erreur_2:
INSTRUCTION_2_1
...
INSTRUCTION_3_N2
...
except Erreur_M:
INSTRUCTION_M_1
...
INSTRUCTION_M_NM
else:
INSTRUCTION_E_1
...
INSTRUCTION_E_P
finally:
INSTRUCTION_F_1
...
INSTRUCTION_F_Q
REMARQUE il faut au moins un bloc except
mais les blocs else
et finally
sont optionnels.
>>> try:
... 1 / 0
...
File "<stdin>", line 3
^
SyntaxError: invalid syntax
REMARQUE la dernière instruction except
, peut, ne pas avoir d’erreur associée, on intercepte alors toutes les erreurs.
>>> try:
... 1 / 0
... except:
... print("Problème")
...
Problème
REMARQUE les instructions du corps principal de try
sont exécutées jusqu’à l’instruction problématique.
>>> try:
... print("Au dessus on exécute")
... x = 1 / 0
... print("Pas au dessous.")
... except ZeroDivisionError:
... print("Mais on passe dans la branche except!")
...
Au dessus on exécute
Mais on passe dans la branche except!
REMARQUE on essaye généralement de réduire au maximum le nombre d’instructions dans le bloc try
(idéalement une seule instruction). Ceci afin de ne pas induire en erreur un lecteur quant à l’instruction responsable du problème (ou au moins de ne pas lui compliquer la vie).
Pour avoir la possibilité d’inclure des instructions qui ne sont exécutées que si tout se passe bien on utilise le bloc else
.
>>> try:
... x = 1 / 0
... except ZeroDivisionError:
... print("PROBLEME!")
... else:
... print("OK")
...
PROBLEME!
>>> try:
... x = 1 / 2
... except ZeroDivisionError:
... print("PROBLEME!")
... else:
... print("OK")
...
OK
REMARQUE le bloc finally
sera exécuté quoi qu’il se passe. C’est à dire même si une exception n’a pas été interceptée.
>>> try:
... x = 1 / 0
... except IndexError:
... print("Interceptée!")
... finally:
... print("FINALEMENT")
...
FINALEMENT
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
Typiquement avant que la syntaxe with
soit introduite, l’ouverture d’un fichier était dans un bloc try
et la fermeture dans finally
.
REMARQUE on peut aussi récupérer le message précis d’erreur au moment de l’interception en utilisant as
.
>>> ls = list()
>>> try:
... ls[0]
... except IndexError as e:
... print(e)
...
list index out of range
REMARQUE les exceptions sont des objets à part entières qui sont cachés dans le module __builtins__
de l’espace de nom global.
>>> from rich import print
>>> from rich.columns import Columns
>>> print(Columns(dir(__builtins__)))
ArithmeticError AssertionError AttributeError BaseException
BlockingIOError BrokenPipeError BufferError BytesWarning
ChildProcessError ConnectionAbortedError ConnectionError ConnectionRefusedError
ConnectionResetError DeprecationWarning EOFError Ellipsis
EnvironmentError Exception False FileExistsError
FileNotFoundError FloatingPointError FutureWarning GeneratorExit
IOError ImportError ImportWarning IndentationError
IndexError InterruptedError IsADirectoryError KeyError
KeyboardInterrupt LookupError MemoryError ModuleNotFoundError
NameError None NotADirectoryError NotImplemented
NotImplementedError OSError OverflowError PendingDeprecationWarning
PermissionError ProcessLookupError RecursionError ReferenceError
ResourceWarning RuntimeError RuntimeWarning StopAsyncIteration
StopIteration SyntaxError SyntaxWarning SystemError
SystemExit TabError TimeoutError True
TypeError UnboundLocalError UnicodeDecodeError UnicodeEncodeError
UnicodeError UnicodeTranslateError UnicodeWarning UserWarning
ValueError Warning ZeroDivisionError __build_class__
__debug__ __doc__ __import__ __loader__
__name__ __package__ __spec__ abs
all any ascii bin
bool breakpoint bytearray bytes
callable chr classmethod compile
complex copyright credits delattr
dict dir divmod enumerate
eval exec exit filter
float format frozenset getattr
globals hasattr hash help
hex id input int
isinstance issubclass iter len
license list locals map
max memoryview min next
object oct open ord
pow print property quit
range repr reversed round
set setattr slice sorted
staticmethod str sum super
tuple type vars zip
On utilise ici rich uniquement pour avoir un affichage tabulaire. On regardera la section Pour aller plus loin si on veut des renseignements sur la hiérarchie entre erreurs.
Finissons cette partie sur une remarque de style. On peut opposer deux façons de coder certaines opérations pouvant générer une exception. Pour illustrer ces deux façons supposons qu’on a récupéré un dictionnaire dico
et qu’on veut appliquer une fonction ma_fonction
et la valeur associée à "clef"
sans être certain que celle-ci existe.
>>> if "clef" in dico:
resultat = ma_fonction(dico["clef"])
>>> try:
valeur = dict["clef"]
except KeyError:
pass
else:
resultat = ma_fonction(valeur)
On va montrer ici très rapidement comment générer soi-même des exceptions. Ceci est effectuée au moyen de l’instruction raise
à laquelle on passe un objet Exception
avec éventuellement un message d’erreur inclus. Donnons un exemple de fonction:
>>> def divise(a, b):
... if b == 0:
... raise ValueError("Pas de division par zéro SVP!")
... return a / b
...
>>> divise(1, 2)
0.5
>>> divise(1, 0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in divise
ValueError: Pas de division par zéro SVP!
REMARQUE lorsqu’on code des fonctions générant des exceptions on les utilise dans le style EAFP. Alors que dans le style LBYL on se retrouve souvent à coder deux fonctions pour une même tache: - la fonction qui vérifie si la tâche est possible - et la fonction qui effectue réellement la tache
Regarder l’exercice 1. pour plus de détails.
REMARQUE il est possible de créer soi-même ses propres exceptions. Ceci est rarement utile à moins de développer un framework on n’en parlera donc pas ici. raise
>>> help("try")
>>> help("raise")
>>> help("EXCEPTIONS")
def recupere_indice(valeur: int, nombres: List[int]) -> bool: “”“Renvoie le premier indice ou nombre vaut valeur.”“” for indice, nombre in enumerate(nombres): if nombre == valeur: return indice
if est_dans_la_liste(valeur=VALEUR, nombres=NOMBRES): RESULTAT = recupere_indice(valeur=VALEUR, nombres=NOMBRES) else: print(“PROBLEME!”) ```
Dans le style EAFP
def recupere_indice(valeur: int, nombres: List[int]) -> int:
"""Renvoie l'indice du premier nombre valant valeur."""
for indice, nombre in enumerate(nombres):
if valeur == nombre:
return indice
raise ValueError("valeur n'est pas un des nombres!")
try:
RESULTAT = recupere_indice(valeur=VALEUR, nombres=NOMBRES)
except ValueError:
print("PROBLEME!")
On voit que d’un côté le style LBYL nécessite presque de coder deux fois la même fonction. D’un autre côté le fait que recupere_indice
génère ValueError
n’est pas du tout lisible dans sa signature. En fait on ne la même pas mentionné dans la documentation (ce qui est clairement une très mauvaise idée).
Une façon de signaler le problème serait de modifier le type de renvoie pour Union(int, ValueError)
. Cependant ce n’est pas tout à fait correct. En effet alors qu’on n’a effectivement la possibilité de renvoyer un objet ValueError
ici on soulève l’exception ValueError
ce qui n’est pas la même chose.