Python & AI Tutorials Logo
Programmation Python

25. Gérer les exceptions avec élégance

Dans le chapitre 24, nous avons appris à lire et comprendre les exceptions lorsqu’elles se produisent. Maintenant, nous allons apprendre à gérer les exceptions avec élégance, en permettant à nos programmes de se remettre des erreurs au lieu de planter. C’est essentiel pour écrire des programmes robustes et conviviaux, capables de gérer des situations inattendues.

Lorsqu’une exception se produit en Python, le flux normal du programme s’arrête immédiatement. Mais que se passerait-il si nous pouvions intercepter cette exception avant qu’elle ne fasse planter notre programme ? Et si nous pouvions répondre à l’erreur, par exemple en demandant à l’utilisateur de réessayer, ou en utilisant une valeur par défaut, ou en journalisant le problème puis en continuant ? C’est exactement ce que permet la gestion des exceptions.

25.1) Utiliser des blocs try et except

25.1.1) La structure de base de try et except

Un bloc try-except est la façon qu’a Python de dire « essayez de faire ceci, et si une exception se produit, faites ceci à la place ». La structure de base ressemble à ceci :

python
try:
    # Code susceptible de lever une exception
    risky_operation()
except:
    # Code exécuté si N’IMPORTE QUELLE exception se produit
    print("Something went wrong!")

Le bloc try contient du code susceptible de lever une exception. Si une exception se produit n’importe où dans le bloc try, Python arrête immédiatement d’exécuter le bloc try et passe au bloc except. Si aucune exception ne se produit, le bloc except est entièrement ignoré.

Voyons un exemple concret. Rappelez-vous du chapitre 24 que tenter de convertir une chaîne invalide en entier lève une ValueError :

python
# Sans gestion d’exceptions - le programme plante
user_input = "hello"
number = int(user_input)  # ValueError: invalid literal for int() with base 10: 'hello'
print("This line never executes")

Maintenant, gérons cette exception avec élégance :

python
# Avec gestion d’exceptions - le programme continue
user_input = "hello"
 
try:
    number = int(user_input)
    print(f"You entered: {number}")
except:
    print("That's not a valid number!")
    number = 0  # Utiliser une valeur par défaut
 
print(f"Using number: {number}")

Sortie :

That's not a valid number!
Using number: 0

Le programme n’a pas planté ! Quand int(user_input) a levé une ValueError, Python est passé au bloc except, a affiché notre message d’erreur, a défini une valeur par défaut, puis a continué avec le reste du programme.

Voici ce qui se passe étape par étape :

Non

Oui

Début du bloc try

Exécuter la conversion int

Exception levée ?

Continuer dans le bloc try

Aller au bloc except

Ignorer le bloc except

Exécuter le code du except

Continuer après try-except

Comprendre le « saut » — ce qui se passe réellement

Quand on dit que Python « saute » vers le bloc except, on veut dire qu’il abandonne l’exécution séquentielle normale. C’est un changement fondamental de la façon dont votre programme s’exécute — pas juste une simple branche comme une instruction if. Voyons cela en détail avec un exemple concret :

python
# Observer le flux d’exécution avec des exceptions
print("1. Starting program")
 
try:
    print("2. Entered try block")
    number = int("hello")  # L’exception se produit ICI
    print("3. After conversion")   # Cette ligne ne s’exécute JAMAIS
    result = number * 2            # Cette ligne ne s’exécute JAMAIS
    print("4. After calculation")  # Cette ligne ne s’exécute JAMAIS
except ValueError:
    print("5. In except block - handling the error")
 
print("6. After try-except block")

Sortie :

1. Starting program
2. Entered try block
5. In except block - handling the error
6. After try-except block

Remarquez que les lignes 3 et 4 ne s’exécutent jamais ! Dès que int("hello") lève une ValueError, Python :

  1. Arrête immédiatement d’exécuter le bloc try — précisément sur la ligne où l’exception s’est produite
  2. Cherche une clause except correspondante capable de gérer ce type d’exception
  3. Saute vers ce bloc except, en ignorant tout le code restant dans le bloc try
  4. Continue après la structure try-except une fois le bloc except terminé

C’est fondamentalement différent d’un flux normal de programme. En exécution normale, Python exécute chaque ligne séquentiellement. Avec une exception, Python abandonne le chemin actuel et prend un itinéraire complètement différent. Sans gestion des exceptions, le programme planterait à la ligne 2 et se terminerait. Avec gestion des exceptions, le programme se remet et continue.

Pourquoi c’est important :

Comprendre ce comportement de « saut » est crucial parce que :

  • Tout code après l’exception dans le bloc try est ignoré — vous ne pouvez pas supposer que les lignes suivantes du bloc try ont été exécutées
  • Des variables peuvent ne pas être initialisées si l’exception se produit avant leur affectation
  • Vous devez anticiper dans quel état se trouve votre programme quand le bloc except s’exécute

25.1.2) Gérer la saisie utilisateur en toute sécurité

L’un des usages les plus courants de la gestion des exceptions est la validation de la saisie utilisateur. Les utilisateurs peuvent taper n’importe quoi, et nous devons gérer les saisies invalides avec élégance. Voici un exemple pratique d’un programme qui demande l’âge d’un utilisateur :

python
# Saisie d’âge sécurisée avec gestion d’exceptions
print("Please enter your age:")
user_input = input()
 
try:
    age = int(user_input)
    print(f"You are {age} years old.")
    
    # Calculer l’année de naissance (en supposant que l’année actuelle est 2024)
    birth_year = 2024 - age
    print(f"You were born around {birth_year}.")
except:
    print("Invalid input! Age must be a number.")
    print("Using default age of 0.")
    age = 0

Si l’utilisateur saisit "25", la sortie est :

Please enter your age:
25
You are 25 years old.
You were born around 1999.

Si l’utilisateur saisit "twenty-five", la sortie est :

Please enter your age:
twenty-five
Invalid input! Age must be a number.
Using default age of 0.

Remarquez comment le programme gère l’erreur avec élégance au lieu de planter avec un traceback. C’est bien meilleur pour l’expérience utilisateur.

25.1.3) Gérer plusieurs opérations dans un bloc try

Vous pouvez mettre plusieurs opérations dans un seul bloc try. Si l’une d’elles lève une exception, Python passe immédiatement au bloc except. Commençons par un exemple simple :

python
# Deux opérations dans le bloc try
print("Enter a number:")
user_input = input()
 
try:
    number = int(user_input)      # Première opération - peut lever ValueError
    result = 100 / number         # Deuxième opération - peut lever ZeroDivisionError
    print(f"100 / {number} = {result}")
except:
    print("Something went wrong!")

Si l’utilisateur saisit "hello", l’exception se produit à la première opération (conversion). Si l’utilisateur saisit "0", l’exception se produit à la deuxième opération (division). Dans les deux cas, notre unique bloc except la capture.

Étendons maintenant cela à trois opérations :

python
# Plusieurs opérations dans le bloc try
print("Enter two numbers to divide:")
numerator_input = input("Numerator: ")
denominator_input = input("Denominator: ")
 
try:
    numerator = int(numerator_input)      # Peut lever ValueError
    denominator = int(denominator_input)  # Peut lever ValueError
    result = numerator / denominator      # Peut lever ZeroDivisionError
    print(f"{numerator} / {denominator} = {result}")
except:
    print("Something went wrong with the calculation!")
    print("Make sure you enter valid numbers and don't divide by zero.")

Si l’utilisateur saisit "10" et "2" :

Enter two numbers to divide:
Numerator: 10
Denominator: 2
10 / 2 = 5.0

Si l’utilisateur saisit "10" et "zero" :

Enter two numbers to divide:
Numerator: 10
Denominator: zero
Something went wrong with the calculation!
Make sure you enter valid numbers and don't divide by zero.

Si l’utilisateur saisit "10" et "0" :

Enter two numbers to divide:
Numerator: 10
Denominator: 0
Something went wrong with the calculation!
Make sure you enter valid numbers and don't divide by zero.

Dans cet exemple, trois choses différentes peuvent mal se passer : la conversion du numérateur peut échouer, la conversion du dénominateur peut échouer, ou la division peut échouer (si le dénominateur vaut zéro). Notre unique bloc except capture tous ces cas. Cependant, cette approche a une limite : nous ne pouvons pas savoir quelle erreur spécifique s’est produite. Nous traiterons cela dans la section suivante.

25.1.4) Le problème des clauses except « nues »

Utiliser except: sans préciser le type d’exception s’appelle une clause except nue (bare except clause). Bien qu’elle capture toutes les exceptions, c’est souvent trop large et cela peut masquer des problèmes inattendus. Considérez cet exemple :

python
# Un except nu capture TOUT — même ce qu’on n’attend pas
numbers = [10, 20, 30]
 
try:
    index = 5  # Nous nous attendons à IndexError si index est hors limites
    value = numbers[index]
    print(f"Value at index {index}: {value}")
except:
    print("Could not access the list element.")

Cela semble raisonnable — nous essayons d’accéder à un élément de liste qui pourrait ne pas exister. Mais que se passe-t-il s’il y a une faute de frappe dans notre code ?

python
# Et s’il y a une faute de frappe dans notre code ?
numbers = [10, 20, 30]
 
try:
    index = 2
    value = numbrs[index]  # Typo: 'numbrs' instead of 'numbers'
    print(f"Value at index {index}: {value}")
except:
    print("Could not access the list element.")

Sortie :

Could not access the list element.

Le except nu capture la NameError due à la faute de frappe et affiche "Could not access the list element." — ce qui nous donne une mauvaise information sur ce qui s’est passé ! On pense que l’index est hors limites, mais en réalité il y a une faute de frappe dans le nom de la variable.

Un except nu capture aussi KeyboardInterrupt (quand l’utilisateur appuie sur Ctrl+C) et SystemExit (quand le programme tente de se terminer), qui ne devraient généralement pas être capturées. Pour ces raisons, il vaut mieux capturer des exceptions spécifiques, ce que nous allons apprendre maintenant.

25.2) Capturer des exceptions spécifiques

25.2.1) Spécifier les types d’exception

Au lieu de capturer toutes les exceptions avec un except nu, nous pouvons spécifier quels types d’exception nous voulons gérer. Cela rend notre code plus précis et nous aide à répondre de manière appropriée à différentes erreurs :

python
# Capturer un type d’exception spécifique
user_input = "hello"
 
try:
    number = int(user_input)
    print(f"You entered: {number}")
except ValueError:
    print("That's not a valid number!")
    number = 0
 
print(f"Using number: {number}")

Sortie :

That's not a valid number!
Using number: 0

Maintenant notre clause except ne capture que les exceptions ValueError. Si un autre type d’exception se produit (comme une NameError due à une faute de frappe), elle ne sera pas capturée, et nous verrons le traceback complet — ce qui est en fait utile pour le débogage !

La syntaxe est : except ExceptionType:ExceptionType est le nom de la classe d’exception que vous voulez capturer (comme ValueError, TypeError, ZeroDivisionError, etc.).

Erreur fréquente : capturer le mauvais type d’exception

Que se passe-t-il si vous spécifiez un type d’exception qui ne correspond pas à ce qui se produit réellement ? Voyons :

python
# Capturer le mauvais type d’exception
user_input = "hello"
 
try:
    number = int(user_input)  # This raises ValueError
    print(f"You entered: {number}")
except TypeError:  # But we're catching TypeError!
    print("That's not a valid number!")
    number = 0
 
print(f"Using number: {number}")

Sortie :

Traceback (most recent call last):
  File "example.py", line 4, in <module>
    number = int(user_input)
ValueError: invalid literal for int() with base 10: 'hello'

Le programme a planté ! Pourquoi ? Parce que int("hello") lève une ValueError, mais notre clause except ne capture que TypeError. Comme il n’y a pas de clause except correspondante, l’exception n’est pas capturée, et le programme se termine.

C’est en fait utile pendant le développement — si vous capturez le mauvais type d’exception, vous verrez le traceback complet et réaliserez votre erreur. C’est l’une des raisons pour lesquelles capturer des exceptions spécifiques est préférable à utiliser except nu.

Comment éviter cette erreur :

  1. Lire le traceback pour voir quel type d’exception s’est réellement produit
  2. Utiliser ce type d’exception spécifique dans votre clause except
  3. Si vous n’êtes pas sûr, exécutez le code et laissez-le planter — le traceback vous le dira !

25.2.2) Gérer différemment différentes exceptions

Vous pouvez avoir plusieurs clauses except pour gérer différents types d’exception de différentes manières. C’est extrêmement utile lorsque différentes erreurs nécessitent des réponses différentes :

python
# Gestion différente pour différentes exceptions
print("Enter two numbers to divide:")
numerator_input = input("Numerator: ")
denominator_input = input("Denominator: ")
 
try:
    numerator = int(numerator_input)
    denominator = int(denominator_input)
    result = numerator / denominator
    print(f"{numerator} / {denominator} = {result}")
except ValueError:
    print("Error: Both inputs must be valid integers.")
    print("You entered something that isn't a number.")
except ZeroDivisionError:
    print("Error: Cannot divide by zero.")
    print("The denominator must be a non-zero number.")

Si l’utilisateur saisit "10" et "abc" :

Enter two numbers to divide:
Numerator: 10
Denominator: abc
Error: Both inputs must be valid integers.
You entered something that isn't a number.

Si l’utilisateur saisit "10" et "0" :

Enter two numbers to divide:
Numerator: 10
Denominator: 0
Error: Cannot divide by zero.
The denominator must be a non-zero number.

Python vérifie chaque clause except dans l’ordre. Lorsqu’une exception se produit, Python trouve la première clause except qui correspond au type d’exception et exécute ce bloc. Les autres clauses except sont ignorées.

Oui

Non

Oui

Non

Oui

Non

Exception levée dans le bloc try

Correspond au premier except ?

Exécuter le premier bloc except

Correspond au deuxième except ?

Exécuter le deuxième bloc except

Correspond au troisième except ?

Exécuter le troisième bloc except

Exception non capturée - remonte

Continuer après try-except

25.2.3) Capturer plusieurs types d’exception dans une clause

Parfois, vous voulez gérer plusieurs types d’exception différents de la même manière. Au lieu d’écrire plusieurs blocs except identiques, vous pouvez capturer plusieurs types d’exception dans une seule clause en les mettant entre parenthèses sous forme de tuple :

python
# Capturer plusieurs types d’exception ensemble
print("Enter a number:")
user_input = input()
 
try:
    number = int(user_input)
    result = 100 / number
    print(f"100 divided by {number} is {result}")
except (ValueError, ZeroDivisionError):
    print("Invalid input or division by zero.")
    print("Please enter a non-zero number.")

Si l’utilisateur saisit "hello" :

Enter a number:
hello
Invalid input or division by zero.
Please enter a non-zero number.

Si l’utilisateur saisit "0" :

Enter a number:
0
Invalid input or division by zero.
Please enter a non-zero number.

À la fois ValueError (conversion invalide) et ZeroDivisionError (division par zéro) sont gérées par la même clause except. C’est utile lorsque des erreurs différentes doivent déclencher la même réponse.

25.2.4) Accéder aux informations d’exception

Parfois, vous avez besoin de connaître plus de détails sur l’exception qui s’est produite. Vous pouvez récupérer l’objet exception en utilisant le mot-clé as. Mais d’abord, comprenons ce qu’est réellement un objet exception.

Qu’est-ce qu’un objet exception ?

Quand Python lève une exception, il ne fait pas que signaler qu’un problème est survenu — il crée un objet qui contient des informations sur l’erreur. Cet objet exception est comme un rapport d’erreur détaillé qui inclut :

  • Le message d’erreur : une description de ce qui s’est mal passé
  • Le type d’exception : quel type d’erreur s’est produit (ValueError, TypeError, etc.)
  • Des attributs supplémentaires : des informations spécifiques selon le type d’exception

Pensez à un objet exception comme à un conteneur qui contient toutes les informations sur une erreur. Tout comme un objet liste(list) contient des éléments et possède des méthodes comme append(), un objet exception contient des informations d’erreur et possède des attributs auxquels vous pouvez accéder.

Quand vous écrivez except ValueError as error:, vous dites à Python : « Si une ValueError se produit, créez une variable appelée error et mettez-y l’objet exception pour que je puisse l’examiner. »

Explorons ce qu’il y a à l’intérieur d’un objet exception :

python
# Examiner le contenu de l’objet exception
try:
    number = int("hello")
except ValueError as error:
    print("Exception caught! Let's examine it:")
    print(f"Type: {type(error)}")
    print(f"String representation: {error}")
    print(f"Args tuple: {error.args}")

Sortie :

Exception caught! Let's examine it:
Type: <class 'ValueError'>
String representation: invalid literal for int() with base 10: 'hello'
Args tuple: ("invalid literal for int() with base 10: 'hello'",)

L’objet exception a :

  • Un type (classe ValueError) — cela vous indique quel type d’erreur s’est produit
  • Une représentation sous forme de chaîne (le message d’erreur) — c’est ce que vous voyez dans les tracebacks
  • Un attribut args (tuple contenant le message et d’éventuels autres arguments) — cela fournit un accès structuré aux détails de l’erreur

Pourquoi c’est important :

Différents types d’exception ont différents attributs qui fournissent des informations spécifiques. Comprendre la structure des objets exception vous aide à extraire des informations utiles pour le débogage ou pour le retour à l’utilisateur :

python
# Différentes exceptions ont différents attributs
numbers = [10, 20, 30]
 
try:
    value = numbers[10]
except IndexError as error:
    print(f"IndexError message: {error}")
    print(f"Exception args: {error.args}")
 
# Maintenant, essayons avec un dictionnaire
grades = {"Alice": 95}
 
try:
    grade = grades["Bob"]
except KeyError as error:
    print(f"KeyError message: {error}")
    print(f"Missing key: {error.args[0]}")

Sortie :

IndexError message: list index out of range
Exception args: ('list index out of range',)
KeyError message: 'Bob'
Missing key: Bob

Remarquez que KeyError inclut la clé réellement manquante dans son message. Différents types d’exception fournissent différentes informations utiles auxquelles vous pouvez accéder via l’objet exception.

25.3) Utiliser else et finally avec les blocs try

25.3.1) La clause else : du code qui ne s’exécute qu’en cas de réussite

La clause else dans un bloc try-except ne s’exécute que si aucune exception ne s’est produite dans le bloc try. C’est utile pour du code qui ne doit s’exécuter que lorsque l’opération risquée réussit :

python
# Utiliser else pour du code exécuté uniquement en cas de réussite
print("Enter a number:")
user_input = input()
 
try:
    number = int(user_input)
except ValueError:
    print("That's not a valid number!")
else:
    # Cela ne s’exécute que si int(user_input) a réussi
    print(f"Successfully converted: {number}")
    squared = number ** 2
    print(f"The square of {number} is {squared}")

Si l’utilisateur saisit "5" :

Enter a number:
5
Successfully converted: 5
The square of 5 is 25

Si l’utilisateur saisit "hello" :

Enter a number:
hello
That's not a valid number!

Pourquoi utiliser else au lieu de simplement mettre le code à la fin du bloc try ? Il y a deux raisons importantes :

  1. Clarté : la clause else rend explicite le fait que ce code ne s’exécute qu’en cas de réussite
  2. Portée des exceptions : les exceptions levées dans la clause else ne sont pas capturées par les clauses except précédentes

Voici un exemple montrant pourquoi le deuxième point est important :

python
# Montrer pourquoi else est utile pour la portée des exceptions
try:
    number_1 = int(input("Enter a number_1: "))
except ValueError:
    print("Invalid input!")
else:
    # Si une erreur se produit ici, elle ne sera pas capturée par le except ci-dessus
    # Cela aide à distinguer les erreurs de saisie des erreurs de traitement
    number_2 = int(input("Enter a number_2: ")) # Could raise ValueError

Si nous mettons number_2 = int(input(...)) dans le bloc try avec number_1, toute ValueError provenant de l’une ou l’autre saisie serait capturée par la même clause except ValueError. Cela rend impossible de savoir quelle saisie a causé le problème.

En mettant number_2 = int(input(...)) dans le bloc else, nous séparons la gestion des erreurs. La clause except ne capture que les erreurs de number_1, tandis que les erreurs de number_2 lèveront une exception non capturée avec un traceback complet — ce qui rend clair que la deuxième saisie a échoué, pas la première.

25.3.2) La clause finally : du code qui s’exécute toujours

La clause finally contient du code qui s’exécute quoi qu’il arrive — qu’une exception se soit produite ou non, qu’elle ait été capturée ou non. C’est essentiel pour des opérations de nettoyage qui doivent toujours avoir lieu :

python
# Utiliser finally pour le nettoyage
print("Enter a number:")
user_input = input()
 
try:
    number = int(user_input)
    result = 100 / number
    print(f"Result: {result}")
except ValueError:
    print("Invalid number!")
except ZeroDivisionError:
    print("Cannot divide by zero!")
finally:
    print("Calculation attempt completed.")

Si l’utilisateur saisit "5" :

Enter a number:
5
Result: 20.0
Calculation attempt completed.

Si l’utilisateur saisit "hello" :

Enter a number:
hello
Invalid number!
Calculation attempt completed.

Si l’utilisateur saisit "0" :

Enter a number:
0
Cannot divide by zero!
Calculation attempt completed.

Le bloc finally s’exécute dans les trois cas ! C’est le comportement clé de finally : il s’exécute toujours, peu importe ce qui s’est passé dans le bloc try.

Non

Oui

Oui

Non

Oui

Non

Début du bloc try

Exception levée ?

Terminer le bloc try

Exception capturée ?

Exécuter le bloc else s’il est présent

Exécuter le bloc except correspondant

L’exception remonte

Exécuter le bloc finally

L’exception a-t-elle été capturée ?

Continuer après try-except-finally

L’exception continue de remonter

25.3.3) Combiner try, except, else et finally

Vous pouvez utiliser les quatre clauses ensemble pour créer une gestion des exceptions complète :

python
# Structure complète de gestion des exceptions
print("Enter a number to calculate its reciprocal:")
user_input = input()
 
try:
    # Opérations risquées
    number = int(user_input)
    reciprocal = 1 / number
except ValueError:
    # Gérer les erreurs de conversion
    print("Error: Input must be a valid integer.")
except ZeroDivisionError:
    # Gérer la division par zéro
    print("Error: Cannot calculate reciprocal of zero.")
else:
    # Code exécuté uniquement en cas de réussite
    print(f"The reciprocal of {number} is {reciprocal}")
    print(f"Verification: {number} × {reciprocal} = {number * reciprocal}")
finally:
    # Code de nettoyage qui s’exécute toujours
    print("Reciprocal calculation completed.")

Si l’utilisateur saisit "4" :

Enter a number to calculate its reciprocal:
4
The reciprocal of 4 is 0.25
Verification: 4 × 0.25 = 1.0
Reciprocal calculation completed.

Si l’utilisateur saisit "hello" :

Enter a number to calculate its reciprocal:
hello
Error: Input must be a valid integer.
Reciprocal calculation completed.

Si l’utilisateur saisit "0" :

Enter a number to calculate its reciprocal:
0
Error: Cannot calculate reciprocal of zero.
Reciprocal calculation completed.

Le flux d’exécution est :

  1. Le bloc try s’exécute toujours en premier
  2. Si une exception se produit, le bloc except correspondant s’exécute
  3. Si aucune exception ne se produit, le bloc else s’exécute (s’il est présent)
  4. Le bloc finally s’exécute toujours en dernier, peu importe ce qui s’est passé

25.4) Lever des exceptions volontairement avec raise

25.4.1) Pourquoi lever des exceptions ?

Jusqu’ici, nous avons capturé des exceptions que Python lève automatiquement. Mais parfois, vous devez lever une exception volontairement dans votre propre code. C’est utile lorsque :

  1. Vous détectez une situation invalide que votre code ne peut pas gérer
  2. Vous voulez imposer des règles ou des contraintes
  3. Vous voulez signaler une erreur au code qui a appelé votre fonction

Lever une exception est la façon qu’a Python de dire : « Je ne peux pas continuer — quelque chose ne va pas, et la personne qui m’a appelé doit s’en occuper. »

La syntaxe est simple : raise ExceptionType("error message")

Voici un exemple de base :

python
# Lever une exception volontairement
age = -5
 
if age < 0:
    raise ValueError("Age cannot be negative!")
 
print(f"Age: {age}")  # Cette ligne ne s’exécute jamais

Sortie :

Traceback (most recent call last):
  File "example.py", line 5, in <module>
    raise ValueError("Age cannot be negative!")
ValueError: Age cannot be negative!

Quand Python rencontre raise, il crée immédiatement une exception et commence à chercher un bloc except pour la gérer. S’il n’y en a pas, le programme se termine avec un traceback.

25.4.2) Lever des exceptions dans des fonctions

Lever des exceptions est particulièrement utile dans des fonctions pour valider les entrées et imposer des contraintes :

python
# Fonction qui valide les entrées en levant des exceptions
def calculate_discount(price, discount_percent):
    """Calculate discounted price.
    
    Args:
        price: Original price (must be positive)
        discount_percent: Discount percentage (must be 0-100)
    
    Returns:
        Discounted price
    
    Raises:
        ValueError: If inputs are invalid
    """
    if price < 0:
        raise ValueError("Price cannot be negative!")
    
    if discount_percent < 0 or discount_percent > 100:
        raise ValueError("Discount must be between 0 and 100!")
    
    discount_amount = price * (discount_percent / 100)
    return price - discount_amount
 
# Utiliser la fonction
try:
    final_price = calculate_discount(100, 20)
    print(f"Final price: ${final_price}")
except ValueError as error:
    print(f"Error: {error}")

Sortie :

Final price: $80.0

Essayons maintenant avec des entrées invalides :

python
# Prix invalide
try:
    final_price = calculate_discount(-50, 20)
    print(f"Final price: ${final_price}")
except ValueError as error:
    print(f"Error: {error}")

Sortie :

Error: Price cannot be negative!
python
# Remise invalide
try:
    final_price = calculate_discount(100, 150)
    print(f"Final price: ${final_price}")
except ValueError as error:
    print(f"Error: {error}")

Sortie :

Error: Discount must be between 0 and 100!

En levant des exceptions, la fonction communique clairement ce qui s’est mal passé. Le code appelant peut ensuite décider comment gérer l’erreur — peut-être en demandant à l’utilisateur une nouvelle saisie, en utilisant des valeurs par défaut, ou en journalisant l’erreur.

25.4.3) Choisir le bon type d’exception

Python possède de nombreux types d’exception intégrés, et choisir le bon rend votre code plus clair. Voici les exceptions les plus couramment utilisées pour la validation :

  • ValueError : à utiliser quand une valeur a le bon type mais une valeur inappropriée (ex. âge négatif, pourcentage invalide)
  • TypeError : à utiliser quand une valeur est d’un type complètement incorrect (ex. chaîne au lieu d’un nombre)
  • KeyError : à utiliser quand une clé de dictionnaire n’existe pas
  • IndexError : à utiliser quand un index de séquence est hors limites

Voici un exemple montrant différents types d’exception :

python
# Utiliser des types d’exception appropriés
def get_student_grade(grades, student_name):
    """Get a student's grade from the grades dictionary.
    
    Args:
        grades: Dictionary mapping student names to grades
        student_name: Name of the student
    
    Returns:
        The student's grade
    
    Raises:
        TypeError: If grades is not a dictionary
        KeyError: If student_name is not in grades
        ValueError: If the grade is invalid
    """
    if not isinstance(grades, dict):
        raise TypeError("Grades must be a dictionary!")
    
    if student_name not in grades:
        raise KeyError(f"Student '{student_name}' not found!")
    
    grade = grades[student_name]
    
    if not (0 <= grade <= 100):
        raise ValueError(f"Invalid grade: {grade} (must be 0-100)")
    
    return grade
 
# Tester avec des données valides
grades = {"Alice": 95, "Bob": 87, "Carol": 92}
 
try:
    grade = get_student_grade(grades, "Alice")
    print(f"Alice's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
    print(f"Error: {error}")

Sortie :

Alice's grade: 95
python
# Tester avec un étudiant manquant
try:
    grade = get_student_grade(grades, "David")
    print(f"David's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
    print(f"Error: {error}")

Sortie :

Error: Student 'David' not found!
python
# Tester avec le mauvais type
try:
    grade = get_student_grade("not a dict", "Alice")
    print(f"Alice's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
    print(f"Error: {error}")

Sortie :

Error: Grades must be a dictionary!

Utiliser le type d’exception approprié aide les autres programmeurs (et votre futur vous) à comprendre quel type d’erreur s’est produit.

25.4.4) Relancer des exceptions

Parfois, vous voulez capturer une exception, faire quelque chose (comme journaliser), puis laisser l’exception continuer de remonter. Vous pouvez faire cela en utilisant raise sans arguments à l’intérieur d’un bloc except :

python
# Relancer une exception après journalisation
def divide_numbers(a, b):
    """Divide two numbers with error logging."""
    try:
        result = a / b
        return result
    except ZeroDivisionError:
        print("ERROR LOG: Division by zero attempted")
        print(f"  Numerator: {a}, Denominator: {b}")
        raise  # Relancer la même exception
 
# Utiliser la fonction
try:
    result = divide_numbers(10, 0)
    print(f"Result: {result}")
except ZeroDivisionError:
    print("Cannot divide by zero!")

Sortie :

ERROR LOG: Division by zero attempted
  Numerator: 10, Denominator: 0
Cannot divide by zero!

L’instruction raise sans arguments relance l’exception qui vient d’être capturée. C’est utile quand vous voulez :

  1. Journaliser ou enregistrer l’erreur
  2. Effectuer un nettoyage
  3. Laisser l’erreur remonter vers l’appelant

25.4.5) Lever des exceptions à partir d’exceptions

Parfois, vous voulez lever une nouvelle exception tout en gérant une autre, en préservant le contexte de l’erreur d’origine. Python 3 fournit la syntaxe raise ... from ... pour cela :

python
# Lever une nouvelle exception à partir d’une exception existante
def load_config(config_dict, key):
    """Load configuration value from dictionary."""
    try:
        config_value = config_dict[key]
        
        # Essayer d’analyser en entier
        parsed_value = int(config_value)
        return parsed_value
        
    except KeyError as error:
        raise RuntimeError(f"Configuration key missing: {key}") from error
    except ValueError as error:
        raise RuntimeError(f"Invalid configuration format for {key}") from error
 
# Utiliser la fonction
config = {"timeout": "30", "retries": "5"}
 
try:
    value = load_config(config, "timeout")
    print(f"Config value: {value}")
except RuntimeError as error:
    print(f"Configuration error: {error}")
    print(f"Original cause: {error.__cause__}")

Sortie :

Config value: 30

Si la clé n’existe pas :

python
try:
    value = load_config(config, "missing_key")
    print(f"Config value: {value}")
except RuntimeError as error:
    print(f"Configuration error: {error}")
    print(f"Original cause: {error.__cause__}")

Sortie :

Configuration error: Configuration key missing: missing_key
Original cause: 'missing_key'

Le mot-clé from relie la nouvelle exception à l’exception d’origine. Cela crée une chaîne d’exceptions qui aide au débogage — vous pouvez voir à la fois ce qui s’est mal passé à un niveau élevé (erreur de configuration) et quelle en était la cause sous-jacente (clé introuvable).


La gestion des exceptions est l’un des outils les plus importants pour écrire des programmes fiables. En utilisant des blocs try-except, vous pouvez anticiper les problèmes, les gérer avec élégance et offrir une meilleure expérience à vos utilisateurs. Rappelez-vous :

  • Utilisez try-except pour gérer avec élégance les erreurs attendues
  • Capturez des types d’exception spécifiques plutôt que d’utiliser except nu
  • Utilisez else pour du code qui ne doit s’exécuter qu’en cas de réussite
  • Utilisez finally pour du code de nettoyage qui doit toujours s’exécuter
  • Levez des exceptions dans votre propre code pour signaler des problèmes
  • Choisissez des types d’exception appropriés pour rendre les erreurs claires
  • Fournissez des messages d’erreur utiles qui expliquent ce qui s’est mal passé

Dans le prochain chapitre, nous apprendrons des techniques de programmation défensive qui combinent la gestion des exceptions avec la validation des entrées et d’autres stratégies pour rendre nos programmes encore plus robustes.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai