Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
■ FORMATION PYTHON PROFESSIONNELLE
Gestion des Exceptions
& Robustesse du Code
try · except · else · finally · raise · exceptions personnalisées
■ Au programme
■ Chapitre 1 · Pourquoi les exceptions sont inévitables
■ Chapitre 2 · Les exceptions Python les plus courantes
■ Chapitre 3 · La structure try...except
■ Chapitre 4 · Les clauses else et finally
■ Chapitre 5 · Lever ses propres exceptions avec raise
■ Chapitre 6 · Bonnes pratiques & application QA/Selenium
■ Chapitre 7 · Projet professionnel complet
Niveau : Intermédiaire | Exemples : Finance, RH, E-commerce, QA / Selenium
© Formation Python Professionnelle Page 1
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
Introduction
Jusqu'ici, nos programmes supposaient que tout se passait toujours comme prévu. Mais dans la
réalité professionnelle, c'est rarement le cas. Les utilisateurs font des erreurs de saisie, des fichiers
sont manquants, des connexions réseau échouent, des serveurs ne répondent pas. Un programme
qui plante au moindre imprévu n'est pas un programme professionnel.
La gestion des exceptions est l'art de prévoir ces imprévus et d'y répondre avec élégance — sans
arrêt brutal du programme. C'est une compétence centrale pour tout développeur Python, et
particulièrement critique en QA (Quality Assurance) où des tests fragiles produisent des rapports
d'erreur inutilisables.
■■ Ce module couvre la gestion des exceptions de A à Z avec des exemples professionnels
réels issus de la finance, du e-commerce, des RH et des tests automatisés avec Selenium.
© Formation Python Professionnelle Page 2
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
CHAPITRE 1
Pourquoi les Exceptions sont Inévitables
La réalité de l'exécution d'un programme en production
Le monde réel est imprévisible
Un programme en production interagit constamment avec l'extérieur : des utilisateurs humains qui
font des erreurs, des fichiers qui peuvent ne pas exister, des bases de données qui peuvent être
indisponibles, des APIs qui peuvent renvoyer des données inattendues. Aucun code, même le plus
rigoureux, ne peut contrôler ces facteurs externes.
Ce qui se passe SANS gestion des exceptions
■ sans_exceptions.py
# ■■■ Scénario : calculatrice de conversion de devise ■■■■■■■■■■■■■■
taux_euro_mad = 10.85
montant_str = input('Montant en EUR à convertir : ')
montant = float(montant_str) # ← point de danger n°1
resultat = montant / 0 # ← point de danger n°2
print(f'Résultat : {resultat} MAD')
■ Sortie (crash)
# Si l'utilisateur tape 'abc' :
ValueError: could not convert string to float: 'abc'
Traceback (most recent call last):
File "[Link]", line 5, in <module>
montant = float(montant_str)
# Le programme s'arrête BRUTALEMENT. L'utilisateur voit une erreur
technique.
# En production, c'est inacceptable.
■ Un programme qui plante avec un Traceback brut est une mauvaise expérience utilisateur
ET une faille de sécurité potentielle — le stack trace révèle la structure interne de votre
application à l'extérieur.
© Formation Python Professionnelle Page 3
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
CHAPITRE 2
Les Exceptions Python les Plus Courantes
Identifier le bon type d'erreur pour mieux la gérer
Python dispose d'une hiérarchie d'exceptions intégrées. Connaître les plus courantes est
indispensable pour écrire des blocs except précis et efficaces.
Exception Déclenchée quand... Exemple concret
ValueError int('abc') → impossible de convertir
Type correct, mais valeur inappropriée
TypeError Opération sur un type incompatible '5' + 3 → str + int impossible
IndexError Index hors limites d'une séquence liste[10] sur une liste de 5 éléments
KeyError Clé inexistante dans un dictionnaire mon_dict['email'] si 'email' absent
FileNotFoundError Fichier ou répertoire introuvable open('[Link]') si fichier absent
ZeroDivisionError Division ou modulo par zéro 100 / 0 ou 100 % 0
AttributeError Attribut ou méthode inexistant sur un [Link]()
objet → NoneType n'a pas upper()
ImportError Module introuvable à l'import import selenium si non installé
PermissionError Droits insuffisants sur un fichier/dossier
Écrire dans un fichier en lecture seule
TimeoutError Délai d'attente dépassé (réseau, I/O) Connexion API qui ne répond pas
Exception Classe parente de toutes les exceptions
Utilisée comme filet de sécurité générique
Visualisation de la hiérarchie
Toutes les exceptions héritent de BaseException. Comprendre cette hiérarchie explique pourquoi attraper
Exception intercepte aussi ValueError, TypeError, etc.
■ Hiérarchie des exceptions Python
BaseException
■■■ Exception ← attrape presque tout
■■■ ValueError ← mauvaise valeur
■■■ TypeError ← mauvais type
■■■ IndexError ← index hors limites
■■■ KeyError ← clé manquante
■■■ FileNotFoundError ← fichier absent
■■■ ZeroDivisionError ← division par zéro
■■■ AttributeError ← attribut manquant
■■■ ImportError ← module absent
© Formation Python Professionnelle Page 4
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
■■■ ... (50+ autres) ← exceptions spécialisées
■ Exemple professionnel n°1 : Identifier le bon type d'erreur
■ identifier_exceptions.py
# ■■■ Diagnostic d'exceptions – Système de Paie
■■■■■■■■■■■■■■■■■■■■■
employes = {'E001': 4500, 'E002': 3200, 'E003': 5800}
heures = [8, 7.5, 'N/A', 6, 10] # données corrompues !
jours = 0 # erreur de configuration
# TypeError – mauvais type dans le calcul
try:
total = heures[2] * 25 # 'N/A' * 25 → TypeError
except TypeError as e:
print(f'TypeError : {e}')
# IndexError – index hors limites
try:
print(heures[99])
except IndexError as e:
print(f'IndexError : {e}')
# KeyError – clé manquante dans le dict
try:
salaire = employes['E999']
except KeyError as e:
print(f'KeyError : {e}')
# ZeroDivisionError – division par zéro
try:
moyenne = sum([Link]()) / jours
except ZeroDivisionError as e:
print(f'ZeroDivisionError : {e}')
■ Sortie console
TypeError : can't multiply sequence by non-int of type 'str'
IndexError : list index out of range
KeyError : 'E999'
ZeroDivisionError : division by zero
© Formation Python Professionnelle Page 5
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
CHAPITRE 3
La Structure try...except
Attraper les erreurs avant qu'elles ne crashent le programme
Syntaxe et fonctionnement
■ Syntaxe de base
try:
# ■■ Bloc TRY
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
# Code risqué : peut lever une exception
instruction_risquee()
except NomException:
# ■■ Bloc EXCEPT
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
# Exécuté UNIQUEMENT si NomException est levée dans le try
gerer_l_erreur()
Approche 1 : plusieurs blocs except (recommandée)
Utilisez plusieurs blocs except quand chaque type d'erreur nécessite un traitement différent. Python
teste les blocs dans l'ordre et s'arrête au premier qui correspond.
■ approche_multiple_except.py
# ■■■ Calculatrice de Conversion Devise – Version Robuste
■■■■■■■■■■■
def convertir_devise(montant_str, taux, devise_cible='MAD'):
"""Convertit un montant EUR vers une devise cible."""
try:
montant = float(montant_str) # risque : ValueError
resultat = montant / taux # risque : ZeroDivisionError
return round(resultat, 2)
except ValueError:
# Traitement spécifique : saisie non numérique
print(f"■ '{montant_str}' n'est pas un nombre valide.")
return None
except ZeroDivisionError:
# Traitement spécifique : taux = 0 (configuration invalide)
print('■ Taux de conversion invalide (zéro).')
return None
© Formation Python Professionnelle Page 6
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
# ■■ Tests ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■
print(convertir_devise('250', 10.85))
# → 23.04 MAD
print(convertir_devise('abc', 10.85))
# → ValueError géré
print(convertir_devise('250', 0))
# → ZeroDivisionError géré
Approche 2 : tuple d'exceptions + capture de l'objet
Quand plusieurs exceptions provoquent le même traitement, regroupez-les dans un tuple. La clause
as e capture l'objet exception pour accéder au message d'erreur précis.
■ tuple_exceptions.py
try:
montant = float(input('Montant EUR : '))
resultat = montant / float(input('Taux : '))
print(f'Résultat : {resultat:.2f} MAD')
except (ValueError, ZeroDivisionError) as e:
# as e → capture le message d'erreur exact de Python
print(f'■ Erreur de calcul : {e}')
print(' Veuillez réessayer avec des valeurs valides.')
■■ La clause 'as e' est très utile pour le logging (journalisation). En production, on enregistre
souvent 'str(e)' ou 'repr(e)' dans un fichier de log plutôt que d'afficher l'erreur à l'utilisateur.
■ Exemple professionnel n°2 : Import de données CSV – Département Finance
■ import_csv_securise.py
# ■■■ Import CSV Sécurisé – Rapport Financier
■■■■■■■■■■■■■■■■■■■■■■■
import csv
def importer_transactions(chemin_fichier):
"""
Importe les transactions depuis un fichier CSV.
Retourne une liste de dict ou None en cas d'erreur.
"""
transactions = []
try:
with open(chemin_fichier, 'r', encoding='utf-8') as f:
lecteur = [Link](f)
© Formation Python Professionnelle Page 7
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
for ligne in lecteur:
montant = float(ligne['montant'])
[Link]({
'id' : ligne['id'],
'client' : ligne['client'],
'montant': montant,
'statut' : ligne['statut']
})
return transactions
except FileNotFoundError:
print(f'■ Fichier introuvable : {chemin_fichier}')
print(' Vérifiez le chemin et relancez l\'import.')
return None
except KeyError as e:
print(f'■ Colonne manquante dans le CSV : {e}')
print(' Colonnes attendues : id, client, montant, statut')
return None
except ValueError as e:
print(f'■ Valeur non numérique dans la colonne montant : {e}')
return None
# ■■ Utilisation
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
data = importer_transactions('transactions_juin_2025.csv')
if data:
print(f'■ {len(data)} transactions importées.')
total = sum(t['montant'] for t in data)
print(f' Total : {total:,.2f} MAD')
© Formation Python Professionnelle Page 8
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
CHAPITRE 4
Aller Plus Loin : else et finally
La structure complète try · except · else · finally
La structure complète
■ Structure complète
try:
# Code risqué
operation_risquee()
except UneException:
# Exécuté SI une UneException est levée
gerer_erreur()
else:
# Exécuté UNIQUEMENT si AUCUNE exception n'a été levée
# Idéal pour le code qui dépend du succès de try
traiter_resultat()
finally:
# Exécuté TOUJOURS – avec ou sans exception
# Nettoyage : fermer fichier, connexion DB, session navigateur...
nettoyer_ressources()
Clause Quand s'exécute-t-elle ? Usage typique
try Toujours (code principal) Code risqué à surveiller
except Uniquement si exception levée Gérer l'erreur, logger, fallback
else Uniquement si AUCUNE exception Code post-succès (traitement du résultat)
finally TOUJOURS (avec ou sans exception) Nettoyage : close(), déconnexion, screenshot
■ Exemple professionnel n°3 : Lecture de fichier de configuration
■ charger_config.py
# ■■■ Lecture Config Sécurisée – Application de Paie
■■■■■■■■■■■■■■■
import json
def charger_configuration(chemin):
"""
Charge un fichier JSON de configuration.
Illustre l'usage complet de try/except/else/finally.
© Formation Python Professionnelle Page 9
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
"""
fichier = None
try:
fichier = open(chemin, 'r', encoding='utf-8')
config = [Link](fichier)
except FileNotFoundError:
print(f'■ Config introuvable : {chemin}')
print(' Chargement de la configuration par défaut...')
return {'taux_cnss': 0.0448, 'taux_amo': 0.0226, 'devise': 'MAD'}
except [Link] as e:
print(f'■ Fichier JSON corrompu : {e}')
return None
else:
# S'exécute UNIQUEMENT si open() ET [Link]() ont réussi
print(f'■ Configuration chargée : {len(config)} paramètres.')
return config
finally:
# S'exécute TOUJOURS – on ferme le fichier quoi qu'il arrive
if fichier:
[Link]()
print(' Fichier de config fermé proprement.')
# ■■ Tests ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■
config = charger_configuration('config_paie.json')
if config:
print(f"Devise configurée : {[Link]('devise', 'MAD')}")
■ Sorties console (2 scénarios)
# Cas 1 : fichier présent et valide
■ Configuration chargée : 5 paramètres.
Fichier de config fermé proprement.
Devise configurée : MAD
# Cas 2 : fichier absent
■ Config introuvable : config_paie.json
Chargement de la configuration par défaut...
Fichier de config fermé proprement.
Devise configurée : MAD
■ Le mot-clé 'with' (context manager) gère automatiquement la fermeture des fichiers —
c'est l'alternative moderne à finally pour les fichiers. Exemple : 'with open(chemin) as f:'
ferme f automatiquement, même en cas d'erreur.
© Formation Python Professionnelle Page 10
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
■ Exemple professionnel n°4 : Connexion à une base de données
■ connexion_bdd_securisee.py
# ■■■ Connexion BDD Sécurisée – Simulation SQLite
■■■■■■■■■■■■■■■■■■■
import sqlite3
def executer_requete(chemin_db, requete, params=()):
"""
Exécute une requête SQL de façon sécurisée.
Illustre : connexion → requête → résultat → déconnexion garantie.
"""
connexion = None
try:
connexion = [Link](chemin_db)
curseur = [Link]()
[Link](requete, params)
resultats = [Link]()
except [Link] as e:
print(f'■ Erreur SQL : {e}')
return None
except [Link] as e:
print(f'■ Erreur base de données : {e}')
return None
else:
# Code qui dépend du succès de la requête
print(f'■ Requête OK – {len(resultats)} enregistrement(s) trouvé(s).')
return resultats
finally:
# La connexion est TOUJOURS fermée – même si la requête plante
if connexion:
[Link]()
print(' Connexion BDD fermée.')
# ■■ Utilisation
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
employes = executer_requete(
'rh_techmaroc.db',
'SELECT nom, salaire FROM employes WHERE departement = ?',
('Ingénierie',)
)
© Formation Python Professionnelle Page 11
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
CHAPITRE 5
Lever ses Propres Exceptions : raise
raise · exceptions personnalisées · hiérarchie custom
Le mot-clé raise
Jusqu'ici, les exceptions étaient levées par Python quand quelque chose se passait mal. Mais vous
pouvez aussi les lever vous-même avec raise quand une valeur est techniquement correcte mais
incohérente dans le contexte métier.
■ syntaxe_raise.py
# Lever une exception standard
raise ValueError('Le salaire ne peut pas être négatif.')
# Lever une exception capturée et la re-propager
try:
resultat = operation_risquee()
except ValueError as e:
print(f'Log : {e}') # journaliser d'abord
raise # re-lever l'exception originale
Créer ses propres classes d'exception
Pour des projets professionnels, créez des exceptions personnalisées qui héritent de Exception.
Elles rendent les erreurs métier explicites, facilitent le débogage et permettent des blocs except
précis.
■ exceptions_personnalisees.py
# ■■■ Exceptions Personnalisées – Domaine RH
■■■■■■■■■■■■■■■■■■■■■■■■■
class ErreurRH(Exception):
"""Classe de base pour toutes les erreurs du module RH."""
pass
class SalaireInvalideError(ErreurRH):
"""Levée quand un salaire a une valeur métier incorrecte."""
def __init__(self, salaire, message=''):
[Link] = salaire
super().__init__(
message or f'Salaire invalide : {salaire} MAD'
)
class DepartementIntrouvableError(ErreurRH):
"""Levée quand un département n'existe pas dans le système."""
© Formation Python Professionnelle Page 12
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
pass
class QuotaCongesDepasseError(ErreurRH):
"""Levée quand un employé dépasse son quota de congés."""
def __init__(self, nom, jours_demandes, solde):
[Link] = nom
self.jours_demandes = jours_demandes
[Link] = solde
super().__init__(
f"{nom} demande {jours_demandes}j mais solde = {solde}j"
)
■ Exemple professionnel n°5 : Système de gestion des congés
■ gestion_conges.py
# ■■■ Gestion des Congés – RH TechMaroc
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
DEPARTEMENTS_VALIDES = ['Ingénierie', 'Finance', 'RH', 'Commercial']
def valider_employe(nom, salaire, departement):
"""Valide les données d'un employé. Lève des exceptions si invalide."""
if not nom or not isinstance(nom, str):
raise ValueError('Le nom doit être une chaîne non vide.')
if salaire < 0:
raise SalaireInvalideError(salaire)
if salaire < 2772: # SMIG Maroc 2025
raise SalaireInvalideError(
salaire,
f'Salaire {salaire} MAD inférieur au SMIG (2772 MAD).'
)
if departement not in DEPARTEMENTS_VALIDES:
raise DepartementIntrouvableError(
f"Département '{departement}' inconnu. ",
)
def soumettre_conges(nom, solde, jours_demandes):
"""Soumet une demande de congé. Vérifie le quota disponible."""
if jours_demandes <= 0:
raise ValueError('Le nombre de jours doit être positif.')
if jours_demandes > solde:
raise QuotaCongesDepasseError(nom, jours_demandes, solde)
return True
# ■■ Tests des validations
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
try:
© Formation Python Professionnelle Page 13
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
valider_employe('Sara Alami', 4500, 'Ingénierie')
print('■ Employée Sara Alami validée.')
except ErreurRH as e:
print(f'■ Erreur RH : {e}')
try:
valider_employe('Karim', 1500, 'Ingénierie')
except SalaireInvalideError as e:
print(f'■ Salaire : {e}')
try:
valider_employe('Leila', 3200, 'Direction')
except DepartementIntrouvableError as e:
print(f'■ Département : {e}')
try:
soumettre_conges('Youssef', solde=5, jours_demandes=12)
except QuotaCongesDepasseError as e:
print(f'■ Congés : {e}')
■ Sortie console
■ Employée Sara Alami validée.
■ Salaire : Salaire 1500 MAD inférieur au SMIG (2772 MAD).
■ Département : Département 'Direction' inconnu.
■ Congés : Youssef demande 12j mais solde = 5j
© Formation Python Professionnelle Page 14
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
CHAPITRE 6
Bonnes Pratiques & Application QA / Selenium
EAFP · Logging · Tests robustes · NoSuchElementException
Les 4 règles d'or
Soyez spécifique
Évitez 'except:' ou 'except Exception:' vides qui attrapent tout. Attrapez
1 uniquement les exceptions que vous attendez réellement. Un except trop large
peut masquer de vrais bugs.
Gardez les blocs try courts
N'entourez que les 1-3 lignes réellement susceptibles de lever une exception. Un
2 try trop long rend difficile de savoir quelle ligne a causé l'erreur et complique le
débogage.
Principe EAFP : demandez le pardon, pas la permission
3 En Python, il est plus idiomatique d'essayer l'opération et de gérer l'erreur plutôt
que de vérifier d'abord si elle est possible. C'est plus performant et plus lisible.
Ne masquez jamais les erreurs silencieusement
Un except avec juste 'pass' est presque toujours une mauvaise idée. Au
4 minimum, journalisez l'erreur. Une erreur ignorée silencieusement devient un bug
impossible à diagnostiquer en production.
EAFP vs LBYL : comparaison concrète
■ eafp_vs_lbyl.py
# ■■■ Accès à un dictionnaire employé
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
employe = {'nom': 'Sara', 'salaire': 4500}
# ■ LBYL (Look Before You Leap) – Moins Pythonique
if 'email' in employe:
if employe['email']: # 2 vérifications
print(employe['email'])
else:
print('Email vide.')
else:
print('Clé email absente.')
© Formation Python Professionnelle Page 15
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
# ■ EAFP (Easier to Ask Forgiveness than Permission) – Pythonique
try:
print(employe['email']) # 1 seul essai direct
except KeyError:
print('Email non renseigné.')
■ Application QA / Selenium – Tester une déconnexion
En Quality Assurance, la gestion des exceptions est cruciale. Un test qui plante sans message clair est inutile.
Un test bien géré donne un diagnostic précis du problème.
■ test_deconnexion_selenium.py
# ■■■ Test de Déconnexion – Framework QA avec Selenium
■■■■■■■■■■■■■■
from [Link] import (
NoSuchElementException,
TimeoutException,
WebDriverException
)
# ■■ Exception personnalisée pour le contexte QA
■■■■■■■■■■■■■■■■■■■■■
class UtilisateurNonConnecteError(Exception):
"""Levée si l'utilisateur n'est pas connecté lors d'un test."""
pass
class EchecTest(Exception):
"""Levée pour marquer explicitement l'échec d'un cas de test."""
pass
def deconnecter_utilisateur(driver):
"""
Scénario de test : cliquer sur le bouton Déconnexion.
Gère les cas : succès, bouton absent, timeout, erreur driver.
"""
try:
# Chercher le bouton de déconnexion (risque : absent)
bouton = driver.find_element_by_id('btn-logout')
[Link]()
except NoSuchElementException:
# Bouton absent → l'utilisateur n'est probablement pas connecté
raise UtilisateurNonConnecteError(
'Bouton #btn-logout introuvable – session non ouverte ?'
)
except TimeoutException:
raise EchecTest('Timeout : la page n\'a pas répondu en temps voulu.')
© Formation Python Professionnelle Page 16
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
except WebDriverException as e:
raise EchecTest(f'Erreur WebDriver inattendue : {e}')
else:
# S'exécute uniquement si le clic a réussi
print('■ TEST PASSÉ : déconnexion réussie.')
finally:
# TOUJOURS : screenshot pour le rapport de bug
driver.save_screenshot('rapport_deconnexion.png')
print(' Screenshot sauvegardé → rapport_deconnexion.png')
# ■■ Exécution du test
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
try:
deconnecter_utilisateur(mon_driver)
except UtilisateurNonConnecteError as e:
print(f'■ PRÉCONDITION ÉCHOUÉE : {e}')
print(' → Vérifiez que le test de connexion précédent a réussi.')
except EchecTest as e:
print(f'■ TEST ÉCHOUÉ : {e}')
■ En QA professionnel, les exceptions personnalisées (EchecTest,
UtilisateurNonConnecteError) permettent aux frameworks de test (pytest, unittest) de
distinguer un ÉCHEC PRÉVU d'une ERREUR INATTENDUE et de générer des rapports
HTML exploitables par toute l'équipe.
© Formation Python Professionnelle Page 17
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
CHAPITRE 7
Projet Professionnel Complet
Système de traitement de commandes e-commerce – Pipeline 100% robuste
Ce projet combine tous les concepts du module : exceptions intégrées, try/except/else/finally,
exceptions personnalisées, raise, bonnes pratiques EAFP, et logging. Il simule le pipeline complet de
traitement d'une commande e-commerce avec gestion d'erreurs à chaque étape.
■ Architecture : chaque fonction a son propre bloc try/except ciblé. Les erreurs métier
utilisent des exceptions personnalisées. Le pipeline principal orchestre tout avec un finally de
nettoyage.
■ pipeline_commande_robuste.py
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■
# PIPELINE DE COMMANDE – TECHSHOP ONLINE
# Gestion complète des exceptions à chaque étape
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■
import json, datetime
# ■■ EXCEPTIONS PERSONNALISÉES DU DOMAINE
■■■■■■■■■■■■■■■■■■■■■■■■■
class ErreurCommande(Exception):
"""Classe de base pour toutes les erreurs du pipeline commande."""
pass
class StockInsuffisantError(ErreurCommande):
def __init__(self, produit, demande, dispo):
super().__init__(f'{produit} : demande={demande}, dispo={dispo}')
class PaiementRefuseError(ErreurCommande):
def __init__(self, raison):
super().__init__(f'Paiement refusé : {raison}')
class AdresseLivraisonInvalideError(ErreurCommande):
pass
# ■■ DONNÉES SIMULÉES
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
CATALOGUE = {
'P001': {'nom': 'Laptop Pro X', 'prix': 8500, 'stock': 5},
'P002': {'nom': 'Souris Sans Fil', 'prix': 250, 'stock': 2},
'P003': {'nom': 'Clavier Méca.', 'prix': 450, 'stock': 0},
}
© Formation Python Professionnelle Page 18
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
# ■■ ÉTAPE 1 : Vérification du stock
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
def verifier_stock(produit_id, quantite):
"""Vérifie la disponibilité. Lève StockInsuffisantError si KO."""
try:
produit = CATALOGUE[produit_id] # risque : KeyError
except KeyError:
raise ErreurCommande(f'Produit inconnu : {produit_id}')
if produit['stock'] < quantite:
raise StockInsuffisantError(
produit['nom'], quantite, produit['stock']
)
return produit
# ■■ ÉTAPE 2 : Calcul du montant
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
def calculer_total(produit, quantite, code_promo=None):
"""Calcule le total TTC avec remise éventuelle."""
PROMOS = {'SAVE10': 0.10, 'SAVE20': 0.20, 'VIP30': 0.30}
sous_total = produit['prix'] * quantite
remise = 0
if code_promo:
try:
taux = PROMOS[code_promo] # risque : KeyError
remise = sous_total * taux
print(f' Code promo {code_promo} appliqué : -{remise:.2f} MAD')
except KeyError:
print(f' ■ Code promo invalide {code_promo!r} – ignoré.')
ht = sous_total - remise
tva = ht * 0.20
total = round(ht + tva, 2)
return sous_total, remise, total
# ■■ ÉTAPE 3 : Validation paiement
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
def valider_paiement(montant, carte):
"""Simule la validation d'un paiement. Lève PaiementRefuseError si
KO."""
try:
numero = [Link]('numero', '')
solde = float([Link]('solde', 0))
except (KeyError, ValueError) as e:
raise ErreurCommande(f'Carte bancaire invalide : {e}')
if len(str(numero)) != 16:
raise PaiementRefuseError('numéro de carte invalide (16 chiffres
requis)')
© Formation Python Professionnelle Page 19
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
if solde < montant:
raise PaiementRefuseError(
f'solde insuffisant ({solde:.2f} MAD < {montant:.2f} MAD)'
)
return True
# ■■ ÉTAPE 4 : Validation adresse
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
def valider_adresse(adresse):
"""Valide l'adresse de livraison."""
champs_requis = ['rue', 'ville', 'code_postal']
manquants = [c for c in champs_requis if not [Link](c)]
if manquants:
raise AdresseLivraisonInvalideError(
f"Champs manquants : {', '.join(manquants)}"
)
return True
# ■■ PIPELINE PRINCIPAL
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
def passer_commande(client, produit_id, quantite, carte, adresse,
promo=None):
"""
Pipeline complet : stock → calcul → paiement → adresse → confirmation.
Gère toutes les erreurs possibles à chaque étape.
"""
commande_id = None
try:
print(f'\n=== COMMANDE – {client} ===')
# Étape 1 : stock
produit = verifier_stock(produit_id, quantite)
print(f'■ Stock OK : {produit["nom"]} x{quantite}')
# Étape 2 : calcul
sous_total, remise, total = calculer_total(produit, quantite, promo)
print(f'■ Total TTC : {total:.2f} MAD')
# Étape 3 : paiement
valider_paiement(total, carte)
print('■ Paiement autorisé.')
# Étape 4 : adresse
valider_adresse(adresse)
print('■ Adresse de livraison validée.')
except StockInsuffisantError as e:
print(f'■ STOCK INSUFFISANT : {e}')
return None
except PaiementRefuseError as e:
© Formation Python Professionnelle Page 20
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
print(f'■ PAIEMENT REFUSÉ : {e}')
return None
except AdresseLivraisonInvalideError as e:
print(f'■ ADRESSE INVALIDE : {e}')
return None
except ErreurCommande as e:
print(f'■ ERREUR COMMANDE : {e}')
return None
except Exception as e:
# Filet de sécurité pour les erreurs imprévues (logger en prod)
print(f'■ ERREUR INATTENDUE : {type(e).__name__}: {e}')
return None
else:
# Tout a réussi : confirmer la commande
import random
commande_id = f'CMD-{[Link](10000,99999)}'
CATALOGUE[produit_id]['stock'] -= quantite # MAJ stock
print(f'■ COMMANDE CONFIRMÉE : {commande_id}')
return commande_id
finally:
# TOUJOURS : journaliser la tentative
horodatage = [Link]().strftime('%H:%M:%S')
statut = 'OK' if commande_id else 'ECHEC'
print(f' [LOG {horodatage}] {client} | {produit_id} | {statut}')
# ■■ TESTS DU PIPELINE
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
carte_ok = {'numero': '1234567890123456', 'solde': 15000}
carte_ko = {'numero': '1234567890123456', 'solde': 100}
adresse_ok = {'rue': '12 Bd Zerktouni', 'ville': 'Casablanca',
'code_postal': '20000'}
adresse_ko = {'rue': '12 Bd Zerktouni'}
passer_commande('Karim Benali', 'P001', 2, carte_ok, adresse_ok,
'SAVE10')
passer_commande('Sara Alami', 'P003', 1, carte_ok, adresse_ok)
passer_commande('Leila Haddad', 'P001', 1, carte_ko, adresse_ok)
passer_commande('Youssef Idris', 'P002', 1, carte_ok, adresse_ko)
© Formation Python Professionnelle Page 21
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
Récapitulatif Général
Élément Syntaxe Rôle Quand l'utiliser
try try: Bloc de code risqué à surveillerToujours en premier
except (simple) except ValueError: Gère UN type d'exception précis
Traitement spécifique par type
except (tuple) except (V, Z) as e: Gère plusieurs exceptions identiquement
Même message d'erreur pour N exceptions
except (as e) except Exception as e: Capture l'objet exception (message)
Pour logger str(e) ou repr(e)
else else: Code exécuté si AUCUNE exception
Traitement du résultat du try
finally finally: Code exécuté TOUJOURS Fermer fichier, BDD, session, screenshot
raise raise MonErreur('msg') Lever une exception manuellement
Règles métier, validation des données
Exception custom class MonErr(Exception):
Exception
pass avec nom métier explicite
Projets pro, bibliothèques, frameworks QA
EAFP try/except (vs if/else) Essayer d'abord, gérer si erreur
Accès dict, fichiers, conversions de types
Les 5 erreurs classiques à éviter absolument
■ except: seul (sans type)
Problème : Attrape TOUT y compris KeyboardInterrupt. Cache des bugs graves.
■ Solution : Toujours spécifier le type : except ValueError:
■ except Exception as e: pass
Problème : L'erreur est avalée silencieusement. Impossible à déboguer en production.
■ Solution : Au minimum : print(e) ou [Link](e)
■ Bloc try trop grand
Problème : Entourer 20 lignes dans un try rend impossible de savoir laquelle plante.
■ Solution : try ne couvre que 1-3 lignes réellement risquées
■ Oublier finally pour les ressources
Problème : Un fichier ou une connexion BDD non fermé(e) = fuite mémoire en production.
■ Solution : Utilisez finally ou le context manager 'with'
■ raise sans message explicite
Problème : raise Exception() sans message ne dit rien à celui qui debug le code.
■ Solution : raise ValueError('Salaire négatif reçu : -500')
© Formation Python Professionnelle Page 22
Formation Python · Gestion des Exceptions try · except · else · finally · raise · exceptions personnalisées
■ Prochaines étapes recommandées
■ Module logging : journaliser les erreurs dans des fichiers de log
■ Context managers (with) : gestion automatique des ressources
■ pytest : écrire et automatiser des tests unitaires en Python
■ Selenium WebDriver : automatisation de tests d'interface web
■ Décorateurs : créer des gestionnaires d'exception réutilisables
© Formation Python Professionnelle Page 23