Exceptions et assertions en python
Améliorez la qualité de vos implémentations en exploitant les exceptions et les assertions.
La gestion des exceptions est un concept bien souvent ignoré, volontairement ou non, par les débutants. Cela peut se concevoir dans le cadre d’une activité récréative, mais dans un contexte professionnel ou universitaire, la chose devient indispensable. Outre le fait de montrer qu’on maîtrise son code, la gestion des exceptions tient un rôle essentiel dans l’organisation du code. Des exceptions bien identifiées, interceptées et retranscrites à l’utilisateur permettront une meilleure qualité d'implémentation, une maintenance plus aisée du programme et bien sûr un support à l'utilisateur plus productif.
Cela étant dit, la gestion des exceptions peut, au premier abord, paraître assez indigeste. Il est vrai que certains concepts doivent être acquis d’une part et que, plus qu’ailleurs, les bonnes pratiques sont fortement recommandées.
Commençons par un exemple relativement simple :
def est_divisible(candidat: int, diviseur: int) -> bool:
if candidat == 0:
return True
else:
return candidat % diviseur == 0
La fonction ci-dessus a pour objet de retourner Vrai si le candidat passé en paramètre
est divisible par le diviseur, également passé en paramètre. Dans le cas contraire,
la fonction va retourner Faux.
La fonction étant relativement simple, vous avez déjà surement compris qu’un diviseur
initialisé à 0 entrainera la levée d’un vilain message de type “ZeroDivisionError:
integer division or modulo by zero”. Nous pouvons donc gérer cette exception pour
éviter au programme de planter et laisser ainsi la main à l'utilisateur.
La première approche va être d’encapsuler notre code entre les instructions try et except, comme ceci :
def est_divisible(candidat: int, diviseur: int) -> bool:
try:
if candidat == 0:
return True
else:
return candidat % diviseur == 0
except ZeroDivisionError as e:
print("Le diviseur ne peut etre 0 !")
Voici le résultat :

Le code de notre fonction reste inchangé, cependant cette fois l’exception levée par l’instruction candidat % diviseur va être interceptée par le bloc except qui, au lieu d'éditer la sortie brute de l’erreur, va se contenter de suivre nos instructions. Ici nous éditons simplement un message mais, dans un programme plus complexe, nous aurions tout aussi bien pu implémenter une autre alternative.
Nous avons dans le cas de figure précédent adopter une approche passive qui consiste
à tenter de dérouler notre programme et, dans le cas où une chose se passe mal,
d’agir en conséquence. Cette approche est tout à fait pertinente dans des fonctions
courtes et spécialisées.
La seconde approche est plus volontaire, elle consiste à lever soi-même les exceptions
sur la base de conditions ou d’assertion. Elle a le mérite, d’une part de pouvoir
distinguer pour un même type exception divers sources et, d’autre part, de générer
des exceptions non problématiques techniquement parlant mais identifiées comme tel
pour les besoins du projet. Voici un exemple :
def annee_correcte(annee: int) -> bool:
try:
if annee < 1960:
raise ValueError('Annee inferieure a 1960')
elif annee > 2000:
raise ValueError('Annee superieure a 2000')
else:
return True
except (TypeError, ValueError) as e:
print(e)
Ici encore le code est relativement simple, nous voulons simplement contrôler que
l'année passée en paramètre de cette fonction soit bien comprise entre 1960 et 2000.
Pour ce faire nous effectuons des tests en comparant l'année aux bornes 1960 puis 2000.
Si une de ces conditions s'avère vraie, nous générons une exception de type “ValueError”
en indiquant un message explicite.
Comme nous le voyons, le fait que l'année soit inférieure à 1960 ne pose pas de problème
technique en soit, le programme ne va pas planter. Néanmoins, fonctionnellement parlant
nous considérons que c’est un problème et générer un message de la sorte peut être
une solution.
Vous pouvez remarquer au passage que nous avons specifié une exception "TypeError" pour gérer le fait qu'une chaine puisse être passée en paramètre. Il s'agit, là, d'une exception technique dont la gestion rejoint le premier point évoqué plus haut.
Voici le résultat :

Les assertions
Nous aurions pu écrire le code précédent de la sorte :
def annee_correcte(annee: int) -> bool:
try:
assert annee >= 1960, 'Annee inferieure a 1960'
assert annee <= 2000, 'Annee superieure a 2000'
return True
except (TypeError, AssertionError) as e:
print(e)
Bien qu’elle fasse exactement la même chose, cette version a l’air plus condensée.
Elle est également plus lisible. Nous avons utilisé ici l’instruction “assert”
qui permet de vérifier qu’un test retourne Vrai, dans le cas contraire une exception
de type “AssertionError” est levée. Quels sont les avantages de cette instruction ?
Elle est utile pour tester les paramètres d'entrée des fonctions. Comme vous le
voyez, en l’utilisant, nous avons regroupé nos tests en début de code et nous nous
sommes abstenus d’utiliser des instructions “if…” qui, il est vrai, peuvent conduire
rapidement à un certain nombre d'imbrications nuisant à la lisibilité du code.
Cette fonction peut également servir à dérouler des tests unitaires. Il s’agit là
d'un autre sujet que l’on traitera dans un article indépendant.
Voici le résultat :

Spécialisez vos exceptions
Certaines bonnes pratiques sont à privilégier en matière d’exception. La plus importante
d’entre elles consiste à être le plus spécifique possible. En effet la plupart des
exceptions sont des classes héritées de la classe “Exception”, rien ne nous empêche
par exemple de spécifier systématiquement la levée d’exception de type “Exception”.
Néanmoins ce faisant nous aurions deux problèmes :
- les exceptions plus spécifiques à suivre, si elles existent, ne seront pas levées,
- ou au contraire nous aurons l’obligation de capter toutes les exceptions spécifiques en amont avant de capter enfin l’exception générique.
Voici un lien vers les différentes classes d’exception existantes :
https://docs.python.org/fr/3/library/exceptions.html#exception-hierarchy
Retrouvez dans la rubrique "Nos datasets" toutes les données dont vous aurez besoin pour tester et pratiquer !