Comment gérer les exceptions en Python: une introduction visuelle détaillée

Bienvenue! Dans cet article, vous apprendrez à gérer les exceptions en Python.

En particulier, nous couvrirons:

  • Des exceptions
  • Le but de la gestion des exceptions
  • La clause try
  • La clause except
  • La clause else
  • La clause finalement
  • Comment lever des exceptions

Es-tu prêt? Commençons! 😀

1️⃣ Introduction aux exceptions

Nous commencerons par des exceptions:

  • Que sont-ils?
  • Pourquoi sont-ils pertinents?
  • Pourquoi devriez-vous les gérer?

Selon la documentation Python:

Les erreurs détectées lors de l'exécution sont appelées exceptions et ne sont pas inconditionnellement fatales.

Des exceptions sont levées lorsque le programme rencontre une erreur lors de son exécution. Ils perturbent le déroulement normal du programme et le terminent généralement brusquement. Pour éviter cela, vous pouvez les attraper et les manipuler de manière appropriée.

Vous les avez probablement vus lors de vos projets de programmation.

Si vous avez déjà essayé de diviser par zéro en Python, vous devez avoir vu ce message d'erreur:

>>> a = 5/0 Traceback (most recent call last): File "", line 1, in  a = 5/0 ZeroDivisionError: division by zero

Si vous avez essayé d'indexer une chaîne avec un index non valide, vous avez certainement ce message d'erreur:

>>> a = "Hello, World" >>> a[456] Traceback (most recent call last): File "", line 1, in  a[456] IndexError: string index out of range

Ce sont des exemples d'exceptions.

🔹 Exceptions courantes

Il existe de nombreux types d'exceptions, et elles sont toutes soulevées dans des situations particulières. Certaines des exceptions que vous verrez probablement lorsque vous travaillez sur vos projets sont:

  • IndexError - déclenché lorsque vous essayez d'indexer une liste, un tuple ou une chaîne au-delà des limites autorisées. Par exemple:
>>> num = [1, 2, 6, 5] >>> num[56546546] Traceback (most recent call last): File "", line 1, in  num[56546546] IndexError: list index out of range
  • KeyError - déclenché lorsque vous essayez d'accéder à la valeur d'une clé qui n'existe pas dans un dictionnaire. Par exemple:
>>> students = {"Nora": 15, "Gino": 30} >>> students["Lisa"] Traceback (most recent call last): File "", line 1, in  students["Lisa"] KeyError: 'Lisa'
  • NameError - déclenché lorsqu'un nom que vous référencez dans le code n'existe pas. Par exemple:
>>> a = b Traceback (most recent call last): File "", line 1, in  a = b NameError: name 'b' is not defined
  • TypeError - déclenché lorsqu'une opération ou une fonction est appliquée à un objet d'un type inapproprié. Par exemple:
>>> (5, 6, 7) * (1, 2, 3) Traceback (most recent call last): File "", line 1, in  (5, 6, 7) * (1, 2, 3) TypeError: can't multiply sequence by non-int of type 'tuple'
  • ZeroDivisionError - déclenché lorsque vous essayez de diviser par zéro.
>>> a = 5/0 Traceback (most recent call last): File "", line 1, in  a = 5/0 ZeroDivisionError: division by zero

💡 Conseils: pour en savoir plus sur les autres types d'exceptions intégrées, veuillez vous reporter à cet article dans la documentation Python.

🔸 Anatomie d'une exception

Je suis sûr que vous devez avoir remarqué un modèle général dans ces messages d'erreur. Décomposons leur structure générale pièce par pièce:

Tout d'abord, nous trouvons cette ligne (voir ci-dessous). Un retraçage est essentiellement une liste détaillant les appels de fonction qui ont été effectués avant que l'exception ne soit déclenchée.

La traceback vous aide pendant le processus de débogage car vous pouvez analyser la séquence d'appels de fonction qui a abouti à l'exception:

Traceback (most recent call last):

Ensuite, nous voyons cette ligne (voir ci-dessous) avec le chemin d'accès au fichier et la ligne qui a déclenché l'exception. Dans ce cas, le chemin était le shell Python puisque l'exemple a été exécuté directement en IDLE.

File "", line 1, in  a - 5/0

💡 Conseil: si la ligne qui a déclenché l'exception appartient à une fonction, est remplacée par le nom de la fonction.

Enfin, nous voyons un message descriptif détaillant le type d'exception et fournissant des informations supplémentaires pour nous aider à déboguer le code:

NameError: name 'a' is not defined

2️⃣ Gestion des exceptions: objectif et contexte

Vous pouvez vous demander: pourquoi voudrais-je gérer les exceptions? Pourquoi est-ce utile pour moi? En gérant les exceptions, vous pouvez fournir un autre flux d'exécution pour éviter de planter votre programme de manière inattendue.

🔹 Exemple: saisie utilisateur

Imaginez ce qui se passerait si un utilisateur qui travaille avec votre programme entre une entrée invalide. Cela soulèverait une exception car une opération non valide a été effectuée pendant le processus.

Si votre programme ne gère pas cela correctement, il plantera soudainement et l'utilisateur aura une expérience très décevante avec votre produit.

Mais si vous gérez l'exception, vous serez en mesure de fournir une alternative pour améliorer l'expérience de l'utilisateur.

Perhaps you could display a descriptive message asking the user to enter a valid input, or you could provide a default value for the input. Depending on the context, you can choose what to do when this happens, and this is the magic of error handling. It can save the day when unexpected things happen. ⭐️

🔸 What Happens Behind the Scenes?

Basically, when we handle an exception, we are telling the program what to do if the exception is raised. In that case, the "alternative" flow of execution will come to the rescue. If no exceptions are raised, the code will run as expected.

3️⃣ Time to Code: The try ... except Statement

Now that you know what exceptions are and why you should we handle them, we will start diving into the built-in tools that the Python languages offers for this purpose.

First, we have the most basic statement: try ... except.

Let's illustrate this with a simple example. We have this small program that asks the user to enter the name of a student to display his/her age:

students = {"Nora": 15, "Gino": 30} def print_student_age(): name = input("Please enter the name of the student: ") print(students[name]) print_student_age()

Notice how we are not validating user input at the moment, so the user might enter invalid values (names that are not in the dictionary) and the consequences would be catastrophic because the program would crash if a KeyError is raised:

# User Input Please enter the name of the student: "Daniel" # Error Message Traceback (most recent call last): File "", line 15, in  print_student_age() File "", line 13, in print_student_age print(students[name]) KeyError: '"Daniel"'

🔹 Syntax

We can handle this nicely using try ... except. This is the basic syntax:

In our example, we would add the try ... except statement within the function. Let's break this down piece by piece:

students = {"Nora": 15, "Gino": 30} def print_student_age(): while True: try: name = input("Please enter the name of the student: ") print(students[name]) break except: print("This name is not registered") print_student_age()

If we "zoom in", we see the try ... except statement:

try: name = input("Please enter the name of the student: ") print(students[name]) break except: print("This name is not registered")
  • When the function is called, the try clause will run. If no exceptions are raised, the program will run as expected.
  • But if an exception is raised in the try clause, the flow of execution will immediately jump to the except clause to handle the exception.

💡 Note: This code is contained within a while loop to continue asking for user input if the value is invalid. This is an example:

Please enter the name of the student: "Lulu" This name is not registered Please enter the name of the student: 

This is great, right? Now we can continue asking for user input if the value is invalid.

At the moment, we are handling all possible exceptions with the same except clause. But what if we only want to handle a specific type of exception? Let's see how we could do this.

🔸 Catching Specific Exceptions

Since not all types of exceptions are handled in the same way, we can specify which exceptions we would like to handle with this syntax:

This is an example. We are handling the ZeroDivisionError exception in case the user enters zero as the denominator:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except ZeroDivisionError: print("Please enter a valid denominator.") divide_integers()

Ce serait le résultat:

# First iteration Please enter the numerator: 5 Please enter the denominator: 0 Please enter a valid denominator. # Second iteration Please enter the numerator: 5 Please enter the denominator: 2 2.5

Nous gérons cela correctement. Mais ... si un autre type d'exception est déclenché, le programme ne le gérera pas correctement.

Ici, nous avons un exemple de ValueError car l'une des valeurs est un float, pas un int:

Please enter the numerator: 5 Please enter the denominator: 0.5 Traceback (most recent call last): File "", line 53, in  divide_integers() File "", line 47, in divide_integers b = int(input("Please enter the denominator: ")) ValueError: invalid literal for int() with base 10: '0.5'

Nous pouvons personnaliser la façon dont nous gérons différents types d'exceptions.

🔹 Clauses d'exception multiples

Pour ce faire, nous devons ajouter plusieurs exceptclauses afin de gérer différemment différents types d'exceptions.

Selon la documentation Python:

Une instruction try peut avoir plus d'une clause except , pour spécifier des gestionnaires pour différentes exceptions. Au plus un gestionnaire sera exécuté .

Dans cet exemple, nous avons deux clauses except. L'un d'eux gère ZeroDivisionError et l'autre gère ValueError, les deux types d'exceptions qui peuvent être déclenchées dans ce bloc try.

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except ZeroDivisionError: print("Please enter a valid denominator.") except ValueError: print("Both values have to be integers.") divide_integers() 

💡 Astuce: vous devez déterminer quels types d'exceptions peuvent être déclenchées dans le bloc try pour les gérer de manière appropriée.

🔸 Exceptions multiples, une clause d'exception

Vous pouvez également choisir de gérer différents types d'exceptions avec la même clause except.

Selon la documentation Python:

Une clause except peut nommer plusieurs exceptions sous la forme d'un tuple entre parenthèses.

Voici un exemple où nous interceptons deux exceptions (ZeroDivisionError et ValueError) avec la même exceptclause:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except (ZeroDivisionError, ValueError): print("Please enter valid integers.") divide_integers()

La sortie serait la même pour les deux types d'exceptions car elles sont gérées par la même clause except:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers.
Please enter the numerator: 0.5 Please enter valid integers. Please enter the numerator: 

🔹 Gestion des exceptions déclenchées par les fonctions appelées dans la clause try

Un aspect intéressant de la gestion des exceptions est que si une exception est déclenchée dans une fonction qui a été précédemment appelée dans la clause try d'une autre fonction et que la fonction elle-même ne la gère pas, l'appelant la gérera s'il existe une clause except appropriée.

Selon la documentation Python:

Les gestionnaires d'exceptions ne gèrent pas seulement les exceptions si elles se produisent immédiatement dans la clause try, mais également si elles surviennent à l'intérieur de fonctions appelées (même indirectement) dans la clause try.

Voyons un exemple pour illustrer ceci:

def f(i): try: g(i) except IndexError: print("Please enter a valid index") def g(i): a = "Hello" return a[i] f(50)

Nous avons la ffonction et la gfonction. fappelle gdans la clause try. Avec l'argument 50, glèvera une IndexError car l'index 50 n'est pas valide pour la chaîne a.

But g itself doesn't handle the exception. Notice how there is no try ... except statement in the g function. Since it doesn't handle the exception, it "sends" it to f to see if it can handle it, as you can see in the diagram below:

Since f does know how to handle an IndexError, the situation is handled gracefully and this is the output:

Please enter a valid index

💡 Note: If f had not handled the exception, the program would have ended abruptly with the default error message for an IndexError.

🔸 Accessing Specific Details of Exceptions

Exceptions are objects in Python, so you can assign the exception that was raised to a variable. This way, you can print the default description of the exception and access its arguments.

Selon la documentation Python:

La clause except peut spécifier une variable après le nom de l'exception . La variable est liée à une instance d'exception avec les arguments stockés dans instance.args.

Ici, nous avons un exemple (voir ci-dessous) où nous attribuons l'instance de ZeroDivisionErrorà la variable e. Ensuite, nous pouvons utiliser cette variable dans la clause except pour accéder au type de l'exception, son message et ses arguments.

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) # Here we assign the exception to the variable e except ZeroDivisionError as e: print(type(e)) print(e) print(e.args) divide_integers()

La sortie correspondante serait:

Please enter the numerator: 5 Please enter the denominator: 0 # Type  # Message division by zero # Args ('division by zero',)

💡 Astuce: Si vous êtes familier avec les méthodes spéciales, selon la documentation Python: "pour plus de commodité, l'instance d'exception définit de __str__()sorte que les arguments puissent être imprimés directement sans avoir à les référencer .args."

4️⃣ Ajoutons maintenant: la clause «else»

La elseclause est facultative, mais c'est un excellent outil car elle nous permet d'exécuter du code qui ne devrait s'exécuter que si aucune exception n'a été déclenchée dans la clause try.

Selon la documentation Python:

L' instruction tryexcepta une clause else facultative qui, lorsqu'elle est présente, doit suivre toutes les clauses sauf. C'est utile pour le code qui doit être exécuté si la clause try ne lève pas d'exception.

Voici un exemple d'utilisation de la elseclause:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) result = a / b except (ZeroDivisionError, ValueError): print("Please enter valid integers. The denominator can't be zero") else: print(result) divide_integers()

Si aucune exception n'est déclenchée, le résultat est imprimé:

Please enter the numerator: 5 Please enter the denominator: 5 1.0

Mais si une exception est déclenchée, le résultat n'est pas affiché:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers. The denominator can't be zero

💡 Astuce: selon la documentation Python:

L'utilisation de la elseclause est meilleure que l'ajout de code supplémentaire à la tryclause car elle évite d'attraper accidentellement une exception qui n'a pas été déclenchée par le code protégé par l' instruction tryexcept.

5️⃣ La clause «enfin»

La clause finally est la dernière clause de cette séquence. Il est facultatif , mais si vous l'incluez, ce doit être la dernière clause de la séquence. La finallyclause est toujours exécutée, même si une exception a été déclenchée dans la clause try.  

Selon la documentation Python:

Si une finallyclause est présente, la finallyclause s'exécutera en tant que dernière tâche avant la fin de l' tryinstruction. La finallyclause s'exécute, que l' tryinstruction produise ou non une exception.

La clause finally est généralement utilisée pour effectuer des actions de «nettoyage» qui doivent toujours être effectuées. Par exemple, si nous travaillons avec un fichier dans la clause try, nous devrons toujours fermer le fichier, même si une exception a été déclenchée lorsque nous travaillions avec les données.

Voici un exemple de la clause finally:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) result = a / b except (ZeroDivisionError, ValueError): print("Please enter valid integers. The denominator can't be zero") else: print(result) finally: print("Inside the finally clause") divide_integers()

Voici la sortie lorsqu'aucune exception n'a été déclenchée:

Please enter the numerator: 5 Please enter the denominator: 5 1.0 Inside the finally clause

Voici la sortie lorsqu'une exception a été déclenchée:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers. The denominator can't be zero Inside the finally clause

Remarquez comment la finallyclause s'exécute toujours .

❗️Important: remember that the else clause and the finally clause are optional, but if you decide to include both, the finally clause has to be the last clause in the sequence.

6️⃣ Raising Exceptions

Now that you know how to handle exceptions in Python, I would like to share with you this helpful tip: you can also choose when to raise exceptions in your code.

This can be helpful for certain scenarios. Let's see how you can do this:

This line will raise a ValueError with a custom message.

Here we have an example (see below) of a function that prints the value of the items of a list or tuple, or the characters in a string. But you decided that you want the list, tuple, or string to be of length 5. You start the function with an if statement that checks if the length of the argument data is 5. If it isn't, a ValueError exception is raised:

def print_five_items(data): if len(data) != 5: raise ValueError("The argument must have five elements") for item in data: print(item) print_five_items([5, 2])

The output would be:

Traceback (most recent call last): File "", line 122, in  print_five_items([5, 2]) File "", line 117, in print_five_items raise ValueError("The argument must have five elements") ValueError: The argument must have five elements

Notice how the last line displays the descriptive message:

ValueError: The argument must have five elements

You can then choose how to handle the exception with a try ... except statement. You could add an else clause and/or a finally clause. You can customize it to fit your needs.

🔹 Helpful Resources

  • Exceptions
  • Handling Exceptions
  • Defining Clean-up Actions

I hope you enjoyed reading my article and found it helpful. Now you have the necessary tools to handle exceptions in Python and you can use them to your advantage when you write Python code. ? Check out my online courses. You can follow me on Twitter.

⭐️ You may enjoy my other freeCodeCamp /news articles:

  • The @property Decorator in Python: Its Use Cases, Advantages, and Syntax
  • Data Structures 101: Graphs — A Visual Introduction for Beginners
  • Data Structures 101: Arrays — A Visual Introduction for Beginners