--- 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} : Texte qui apparaît 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.") ```