---
title: Le droit aux erreurs
author: Steve Kossouho
---

# Le droit aux erreurs

----

## Exceptions

En Python, une [exception]{.naming} est une erreur qui se produit pendant l'exécution de votre code. 
En tant que développeur, vous pouvez **intercepter** une exception, et dire à Python ce que vous souhaitez faire
afin d'empêcher le programme de se terminer prématurément si ce n'est pas nécessaire.

----

### Jargon des exceptions

- [Lever une exception]{.naming} : Ce que Python effectue lorsqu'on rencontre une erreur à l'exécution d'un script. 
- [Traceback]{.naming} : <span style="color:#F04">Texte qui apparaît</span> dans la sortie d'erreur (en rouge) lorsque votre programme plante à l'exécution. C'est un récapitulatif des exécutions d'instructions qui ont mené directement à l'interruption de l'exécution, accompagné d'informations sur l'exception.

Les exceptions levées par Python sont d'un type qui dépend de l'erreur rencontrée (ex. `TypeError`{.python}). 
Tous ces types font partie d'une hiérarchie dont le type le plus générique est nommé `Exception`.
Théoriquement, un développeur peut créer son propre type d'exception lorsqu'il développe des bibliothèques.

----

### Exemple de gestion d'exception

Dans l'exemple ci-dessous, on effectue une division par zéro, ce qui est toujours une erreur, mais on pourrait accéder 
à un mauvais index de liste, à une clé de dictionnaire inexistante, ou encore à un fichier verrouillé…

```python {.numberLines}
try:
    resultat = 15 / 0  # erreur évidente pour l'exemple
    print("Cette ligne ne sera jamais exécutée.")
except ZeroDivisionError:  # Ici, on intercepte spécifiquement le type d'erreur produit par la division
    print("Le résultat n'a pas pu être calculé.")

print("Fin du programme.")
```

Gestion des erreurs de division par zéro

----

![Hiérarchie des classes d'exception](assets/images/exception-class-hierarchy.png)

----

### Mauvaise pratique sur les exceptions

Il est **ardemment** déconseillé d'utiliser directement la classe `Exception`{.python} (ou ne rien préciser, d'ailleurs) avec la clause `except`{.python}; 
une clause trop générique intercepte **tous** les problèmes rencontrés dans le bloc `try`{.python}. 
Dans certains cas, cela vous fera manquer des erreurs que vous ne souhaitez pas traiter avec le même bloc `except`{.python}.

Une règle d'or de Python indique que dans le cas où vous masquez tous les types d'exception sans distinction, vous
ne devez jamais le faire silencieusement; affichez le problème ou consignez-le quelque part :

```python {.numberLines}
import traceback

try:
    print(15 / 0)
except Exception:  # ou except:
    traceback.print_exc()  # Affiche le traceback dans la console sans planter
```

----

### Un bloc `except`, plusieurs exceptions

On peut même avoir des blocs `except`{.python} prenant en charge plusieurs types d'exceptions, pour cela il suffit d'indiquer 
un tuple de classes d'exceptions à la clause `except`{.python} :

```python {.numberLines}
try:
    pass  # ou faire autre chose
except (TypeError, ValueError):
    pass
```

----

### Un bloc `try`, plusieurs gestions

On peut, à l'instar de la structure `if/elif/else`, faire suivre le bloc `try`{.python} de 
plusieurs blocs `except`{.python} :

```python {.numberLines}
try:
    print("Code à essayer")
except ValueError:
    print("Code pour ValueError")
except TypeError:
    print("Code pour TypeError")
```

Python évaluera la structure de haut en bas. Si une erreur se produit dans le bloc `try`{.python},
Python évaluera d'abord le premier bloc `except`{.python}, de la même manière que pour toutes les structures de code.

----

Si aucun des blocs `except`{.python} ne gère précisément l'erreur qui se produit dans le bloc `try`{.python}, 
alors l'interpréteur Python plante avec un traceback dans la console, comme attendu.

```python {.numberLines}
try:
    1 / 0
except TypeError:  # pas la bonne exception
    pass
except NameError:  # pas la bonne exception non plus
    pass
```

----

### Code exécuté si tout se passe bien

La structure `try`{.python}...`except`{.python} peut être accompagnée d'un bloc `else`{.python}. Ce bloc
sera exécuté si aucun des blocs `except`{.python} précédents n'a été exécuté (comme dans une structure `if`{.python}) :

```python {.numberLines}
try:
    value = input("Saisissez un nombre entier : ")
    value = int(value)
except ValueError:
    value = None
else:
    # Exécuté si le bloc try n'est pas interrompu par une erreur
    print(f"Merci, la valeur est de {value}.")
```

----

### Exécution garantie

Pour finir, il existe un bloc `finally`{.python}, dont le bloc est *toujours* exécuté, même si le programme doit planter :

```python {.numberLines}
try:
    15 / 0
except KeyError:
    pass
finally:
    print("Toujours exécuté")
```

En général, on imagine l'utiliser lorsqu'on souhaite penser à toujours libérer une ressource (connexion, fichier etc.)

----

### Lever une exception

On peut souhaiter également lever soi-même une exception pour notifier un problème à gérer. 
C'est généralement le cas lorsque l'on développe une bibliothèque utilisable par d'autres développeurs.

```python {.numberLines}
def do_something(value):
    if not isinstance(value, int):  # si value n'est pas un entier
        raise TypeError("Value must be an integer.")
```
