Les exceptions

Une vidéo de présentation des exceptions…

Comme d’autres langages, Python dispose d’un mécanisme de gestion des exceptions. 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.

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 rédaction

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 tab par 4 espaces.

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()

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')

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'

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 :

  1. on tente d’exécuter les instructions contenues dans le bloc try ;

  2. si aucune erreur n’est produite, on exécute ensuite les instructions contenues dans les blocs else puis finally ;

  3. sinon, on exécute les instructions contenues dans les blocs except puis finally;

A expérimenter…

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. Vérifier dans un interpréteur interactif.

A expérimenter…

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. Vérifier dans un interpréteur interactif.

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.

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.

Ce qu’il faut retenir

  • En Python, une exception confirme la règle

  • 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

  • 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

  • Les exceptions sont des objets

  • L’acronyme LBYL signifie

  • L’acronyme EAFP signifie

  • Une exception est déclenchée quand une erreur se produit dans le bloc try

  • Une exception est déclenchée quand une erreur se produit dans le bloc except

  • Lorsqu’il n’y a pas d’erreur dans le bloc try, le bloc except est exécuté

  • Lorsqu’il n’y a pas d’erreur dans le bloc try, le bloc else est exécuté

  • Lorsqu’il n’y a pas d’erreur dans le bloc try, le bloc finally est exécuté

  • Lorsqu’il y a une erreur dans le bloc try, le bloc except est exécuté

  • Lorsqu’il y a une erreur dans le bloc try, le bloc else est exécuté

  • Lorsqu’il y a une erreur dans le bloc try, le bloc finally est exécuté

  • Une erreur de syntaxe déclenche une exception

  • L’accès à une référence inconnue déclenche une exception

  • L’accès à un fichier introuvable déclenche une exception