Gestion des erreurs
Lorsqu’on exécute un programme, il peut arriver que des erreurs se produisent. Ces erreurs peuvent être dues à des problèmes de syntaxe, de logique ou d’exécution. Il est important de savoir comment gérer ces erreurs pour éviter que le programme ne plante et pour fournir des messages d’erreur clairs à l’utilisateur.
Les erreurs de syntaxe sont détectées par l’interpréteur ou le compilateur avant l’exécution du programme. Elles se produisent lorsque le code ne respecte pas les règles de la langue de programmation utilisée. Par exemple, une erreur de syntaxe peut se produire si une parenthèse est oubliée ou si un mot-clé est mal orthographié.
Les erreurs de logique se produisent lorsque le programme s’exécute sans erreur de syntaxe, mais ne produit pas le résultat attendu. Ces erreurs sont souvent plus difficiles à détecter car elles ne génèrent pas de messages d’erreur explicites. Elles peuvent être causées par des erreurs dans les algorithmes, des conditions incorrectes ou des variables mal initialisées.
Les erreurs d’exécution se produisent lorsque le programme est en cours d’exécution et rencontre une situation inattendue, comme la division par zéro ou l’accès à un fichier inexistant. Ces erreurs peuvent provoquer l’arrêt du programme et doivent être gérées de manière appropriée pour éviter des plantages.
Nous allons nous concentrer ici sur la gestion des erreurs à l’exécution.
Une exception interrompt systématiquement l’exécution d’un programme
Une exception non gérée interrompt systématiquement l’exécution d’un programme
Une erreur de est détectée avant l’exécution du programme.
Les erreurs de logique génèrent des messages d’erreur peu explicites.
Les erreurs peuvent arrêter un programme.
Les erreurs de syntaxe sont détectées du programme.
On peut intercepter un problème d’exécution avant qu’il n’interrompe le programme
Il est obligatoire d’intercepter un problème d’exécution avant qu’il n’interrompe le programme
Il est souhaitable d’intercepter un problème d’exécution avant qu’il n’interrompe le programme
Langage C
En langage C, les erreurs a l’exécution du programme sont souvent gérées en utilisant des codes de retour et des messages d’erreur. Les fonctions peuvent retourner des valeurs spéciales pour indiquer qu’une erreur s’est produite, et il est courant d’utiliser la fonction perror() pour afficher un message d’erreur correspondant à l’erreur rencontrée.
Le langage C ne possède aucun mécanisme d’exception permettant d’intercepter les erreurs comparable à ceux de langages de plus haut niveau comme Python ou Java. La gestion des erreurs repose sur plusieurs mécanismes, qui peuvent être combinés.
Le langage C possède un mécanisme d’exception.
La fonction
perror()arrête le programme.La gestion des erreurs en C repose sur des et des messages d’erreur.
Les codes de retour
C’est la méthode la plus courante pour signaler une erreur. Une fonction retourne un code indiquant si l’opération a réussi ou échoué. Le code de retour est généralement un entier, où une valeur de zéro indique le succès et une valeur non nulle indique une erreur.
int lire_fichier(const char *nom)
{
FILE *f = fopen(nom, "r");
if (f == NULL)
return -1;
/* ... */
fclose(f);
return 0;
}
int main(void)
{
if (lire_fichier("test.txt") != 0)
{
printf("Erreur\n");
}
}
C’est l’approche privilégiée dans la bibliothèque standard.
Pour indiquer le succès, une fonction retourne généralement
Une valeur indique une erreur.
Vérifier le code de retour d’une fonction est une bonne pratique.
Une fonction qui échoue peut retourner -1 comme code d’erreur.
La variable globale errno
Certaines fonctions de la bibliothèque standard renseignent la variable globale errno.
FILE *f = fopen("test.txt", "r");
if (f == NULL)
{
perror("fopen");
}
Ici, l’erreur est séparée du code de retour.
La variable
errnoest renseignée par fonctions de la bibliothèque standarderrnoest une variable globale.La fonction
perror()affiche un message d’erreur correspondant à
Les paramètres de sortie
En C une fonction ne peut pas retourner plusieurs valeurs. Certaines fonctions retournent donc le résultat via un pointeur et utilisent la valeur de retour pour signaler une erreur.
int division(int a, int b, int *resultat)
{
if (b == 0)
return -1;
*resultat = a / b;
return 0;
}
int main(void)
{
int r;
if (division(10, 2, &r) == 0)
printf("%d\n", r);
else
printf("Division impossible\n");
}
En C, pour retourner plusieurs valeurs, on peut utiliser des
Un paramètre de sortie permet de retourner le résultat d’une fonction.
Dans l’exemple, si
bvaut 0, la fonction retourneEn cas d’erreur la fonction division retourne
Les fonctions abort() et assert()
La fonction abort() arrête immédiatement le programme en cours d’exécution. Elle est souvent utilisée pour signaler des erreurs graves ou irrécupérables.
On utilise la fonction assert() pour vérifier une condition. Si cette condition n’est pas respectée, un affichage est produit dans le terminal, et abort() est déclenchée pour arrêter le programme. Cela est souvent utilisé pour vérifier les préconditions d’une fonction.
#include <assert.h>
#include <stdlib.h>
void f(int *ptr)
{
assert(ptr != NULL); /* Vérifie que ptr n'est pas NULL */
/* ... */
}
int main(void)
{
f(NULL); /* Cela déclenchera l'assertion et arrêtera le programme */
return 0;
}
Le programme ci dessus s’arrête immédiatement.
La fonction
abort()le programmeassert()vérifie une condition.Si une assertion échoue,
abort()est automatiquement appelée.Les assertions sont souvent utilisées pour vérifier les d’une fonction.
Les assertions sont vérifiées
Python
Python dispose d’un mécanisme d’exception intégré qui permet de gérer les erreurs de manière plus structurée et lisible. Les exceptions sont des objets qui représentent des erreurs ou des situations exceptionnelles qui se produisent pendant l’exécution d’un programme. Une exception est une erreur qui se produit lors de l’exécution du programme. Gérer une exception c’est interrompre le cours normal du programme pour déclencher un traitement particulier permettant de poursuivre, ou pas, son exécution.
En Python, une exception confirme la règle
Les exceptions en Python sont des
Une exception interrompt le cours normal du programme.
Gérer une exception permet de l’exécution du programme après capture de l’erreur.
Une exception est déclenchée du programme.
Une vidéo de présentation des exceptions…
Quelques exceptions courantes
Il existe un très grand nombre d’exceptions organisées de façon structurée et hiérarchique. Voici les plus courantes.
Les erreurs de syntaxe
Python n’étant pas un langage compilé, les erreurs dans la rédaction du code ne sont détectées que pendant la phase d’exécution et déclenchent des exceptions.
Ainsi les erreurs de syntaxe sont elles même des exceptions de type SyntaxError.
>>> if x%2 == 0 print('x est pair')
File "<stdin>", line 1
if x%2 == 0 print('x est pair')
^
SyntaxError: invalid syntax
Une exception de type IndentationError se produit lorsque l’on indente mal son programme.
>>> if True:
... print('ligne indentée avec une tabulation')
... print('ligne indentée avec 4 espaces')
File "<stdin>", line 3
print('4 espaces')
^
IndentationError: unindent does not match any outer indentation level
Pour éviter ce type d’erreur, on peut paramétrer son éditeur de texte :
en visualisant les caractères non imprimables ;
en remplaçant le caractère
tabpar 4 espaces.
Une erreur de syntaxe déclenche une exception de type
Python détecte les erreurs de syntaxe pendant l’exécution.
Une
IndentationErrorest déclenchée quand on un programme.Pour éviter les erreurs d’indentation, on peut remplacer le caractère par
Les erreurs de conception
Même si la syntaxe est correcte, d’autres problèmes de conception peuvent survenir.
L’appel à une référence qui n’existe pas est une exception de type NameError.
>>> if x%2 == 0: print('x est pair')
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
Il peut également se produire un problème lors de l’importation d’un module.
>>> import un_module_qui_nexiste_pas
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named 'un_module_qui_nexiste_pas'
ou lorsqu’on applique une fonction a un objet inapproprié.
>>> len(True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'bool' has no len()
Une exception
NameErrorest déclenchée quand on utilise une variableUne
NameErrorest une erreur de syntaxe.Une
ImportErrorest déclenchée lors de l’échec de l’import d’unUne
TypeErrorest déclenchée quand on utilise un objet inapproprié.Quelle exception est déclenchée par
len(True)?
Les exceptions numériques
Au cours de l’exécution d’un programme syntaxiquement correct et bien conçu (en ce sens qu’il ne déclenche pas les exceptions précédentes), il peut arriver qu’une opération conduise à une erreur. C’est par exemple le cas lorsqu’un calcul numérique potentiellement « dangereux » utilise un résultat non connu à l’avance.
>>> res = 0
>>> 1/res
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
ou dépassant les capacités de la machine…
>>> 2.0**4000
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: (34, 'Result too large')
Une
ZeroDivisionErrorest déclenchée lors d’uneUne
OverflowErrorest déclenchée quand un calcul dépasse les capacités de la machine.1/0déclenche une exception de typeLes exceptions numériques peuvent être évitées par des tests préalables dans tous les cas.
L’accès à des ressources externes
Lorsque le programme tente d’accéder à une ressource qui n’existe pas ou qui n’est pas disponible au moment de l’exécution, une exception de type FileNotFoundError est déclenchée.
>>> open('un_fichier_qui_nexiste_pas.txt', 'r')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'un_fichier_qui_nexiste_pas.txt'
Une
FileNotFoundErrorest déclenchée quand on essaie d’accéder à un fichierUne
FileNotFoundErrorest une exception d’exécution.open('inexistant.txt', 'r')déclenche une exception de typeLes ressources externes peuvent ne pas être disponibles lors de l’exécution.
La gestion des exceptions
Après ce tour d’horizon des exceptions les plus courantes, voici le moyen de les gérer.
La philosophie de Python n’est pas de contrôler en amont les conditions d’exécution du programme, ce qui peut être lourd, fastidieux et peu lisible mais de tenter d’exécuter l’opération en apportant une solution de repli au cas où elle ne fonctionnerait pas. Ceci s’effectue avec la construction try - except - else - finally, similaire dans le principe au try-catch-finally de Java :
on tente d’exécuter les instructions contenues dans le bloc
try;si aucune erreur n’est produite, on exécute ensuite les instructions contenues dans les blocs
elsepuisfinally;sinon, on exécute les instructions contenues dans les blocs
exceptpuisfinally;
Le bloc
trycontient les instructions à exécuter.Si une exception se produit dans
try, le blocexceptest exécuté.Le bloc
finallyest exécuté dans tous les cas.Une exception est déclenchée quand une erreur se produit dans le bloc
tryUne exception est déclenchée quand une erreur se produit dans le bloc
exceptLorsqu’il n’y a pas d’erreur dans le bloc
try, le blocexceptest exécutéLorsqu’il n’y a pas d’erreur dans le bloc
try, le blocelseest exécutéLorsqu’il n’y a pas d’erreur dans le bloc
try, le blocfinallyest exécutéLorsqu’il y a une erreur dans le bloc
try, le blocexceptest toujours exécutéLorsqu’il y a une erreur dans le bloc
try, le blocelseest toujours exécutéLorsqu’il y a une erreur dans le bloc
try, le blocfinallyest toujoursexécuté
On considère le code suivant :
1for res in range(3):
2 print("Trying to compute 1 /",res)
3 try:
4 print(" 1/",res, "=", 1/res)
5 except ZeroDivisionError:
6 print(" 1 /",res,"tend vers l'infini")
7 else:
8 print(" Le résultat a une valeur finie")
9 finally:
10 print(' Fin de la division')
Conjecturer le résultat de l’exécution.
Il est nécessaire que VS Code soit démarré sur la machine hôte, pointe vers le répertoire e3-programmation-labs-student et que le container Docker soit lancé.
Créer un fichier
exercices/divparzero.pyet copier le code ci dessus ;Exécuter le programme avec la commande
python3 divparzero.py;Vérifier que le résultat affiché correspond à votre conjecture ;
Lorsque c’est le cas :
git add .
git commit -m "Division par zéro"
git push
On considère maintenant le code suivant :
1for file in ["existing_file.txt", "non_existing_file.txt"]:
2 try:
3 print("Trying to open : ***", file, "***")
4 f = open(file, 'r')
5 print(" le fichier a été trouvé")
6 print(" nombre de caractères :",len(f.read()))
7 except FileNotFoundError:
8 print(" le fichier n'a pas été trouvé")
9 finally:
10 f.close()
11 print(" Fin de l'opération")
Conjecturer le résultat de l’exécution.
Il est nécessaire que VS Code soit démarré sur la machine hôte, pointe vers le répertoire e3-programmation-labs-student et que le container Docker soit lancé.
Créer un fichier
exercices/existing_file.txtet un fichierexercices/fichier_inexistant.pyet copier le code ci dessus ;Exécuter le programme avec la commande
python3 fichier_inexistant.py;Vérifier que le résultat affiché correspond à votre conjecture ;
Lorsque c’est le cas :
git add .
git commit -m "Fichier inexistant"
git push
C’est une bonne pratique que d’encapsuler les opérations non sûres dans des blocs try - except - else - finally.
EAFP vs LBYL
On peut également utiliser les exceptions pour simplifier le flux de traitement. Dans certains langages de programmation (C par exemple), on utilise le paradigme Look Before You Leap (LBYL) qui consiste à effectuer l’ensemble des tests nécessaires avant de déclencher une opération. Les langages comportant une gestion des exceptions peuvent mettre en oeuvre le paradigme Easier to Ask for Forgiveness than Permission (EAFP) dont le principe de base est de tenter l’opération et de réagir si elle échoue.
LBYL
Si on met en oeuvre le paradigme LBYL, on sécurise l’accès à une clé de dictionnaire avec le code suivant.
if "key" in my_dict:
x = my_dict["key"]
else:
handle_missing_key()
En cas de programmation concurrente (multithreads), on peut donc aboutir à une situation de compétition, conduisant un comportement non prédictible. Dans l’exemple qui précède, le dictionnaire peut être modifié entre le test et l’accès à la clé.
EAFP
Pour le paradigme EAFP…
try:
x = my_dict["key"]
except KeyError:
handle_missing_key()
Lorsque l’erreur est peu probable, l’utilisation du paradigme EAFP conduit à de meilleures performances algorithmiques.
LBYL signifie
EAFP signifie
Le paradigme LBYL consiste à tester avant de déclencher une opération.
Le paradigme EAFP consiste à tenter une opération et réagir en cas d’erreur.
En cas de programmation concurrente, le paradigme EAFP est plus sûr que LBYL.
Les exceptions sont des objets
En Python tout est objet, les exceptions ne font pas exception (!) à la règle.
On considère le code suivant.
>>> try:
... a = 1/0
... except Exception as e:
... print(type(e))
... print(dir(e))
...
<class 'ZeroDivisionError'>
[... 'args', 'with_traceback']
Ici la référence e est affectée à l’exception générée, ce qui permet de la manipuler ensuite.
En Python, une exception est un
On peut capturer une exception avec
except ... as e.La variable
econtient l’instance de l’exceptionOn peut appeler
type(e)sur une exception capturée.La classe de base de toutes les exceptions est de type
Il est nécessaire que VS Code soit démarré sur la machine hôte, pointe vers le répertoire e3-programmation-labs-student et que le container Docker soit lancé.
Il est courant que les données numériques soient lues comme des chaines de caractères. Sans traitement, elles ne sont donc pas éligibles aux fonctions arithmétiques et statistiques élémentaires : sum(), statistics.mean(), … Il faut alors effectuer une conversion vers le type numérique adapté lorsque c’est pertinent et ne rien faire si la donnée est effectivement une chaîne de caractères.
Créer un fichier exercices/conversion.py qui contient
une fonction secondaire
parse():
qui prend en argument une chaine de caractère ;
et retourne un nombre décimal, un entier ou une chaine de caractère selon son contenu.
le paradigme EAFP est une alternative au traditionnel LBYL et sera mis en oeuvre pour cette opération de conversion. On s’interrogera sur l’ordonnancement des constructions
tryà mettre en oeuvre. Du général au particulier ? Du particulier au général ?une fonction principale
main()qui appelle la fonctionparse()avec des arguments de nature différente et affiche les résultats.
Vérifier le bon fonctionnement du programme. Une fois qu’il est opérationnel, synchroniser les repos :
git add .
git commit -m "Conversion de type"
git push