backend / python
Fichiers et gestion des exceptions
Explication
Ce que vous allez apprendre
- Lire et écrire un fichier texte ou JSON en toute sécurité avec
with open(...) - Comprendre pourquoi
withgarantit la fermeture d'une ressource même en cas d'erreur - Distinguer les rôles précis de
try,except,elseetfinally - Lever vos propres exceptions métier avec des messages clairs
- Chaîner les exceptions avec
raise ... from ...pour préserver la cause d'origine
Dans quel contexte ?
Un développeur écrit une fonction charger_config("config.json") appelée au démarrage d'une application. Le jour où ce fichier est absent ou mal formé (une virgule oubliée dans le JSON), le programme plante avec une trace d'erreur brute et incompréhensible pour l'utilisateur final. Cette leçon montre comment intercepter FileNotFoundError et json.JSONDecodeError pour afficher un message utile, voire proposer une configuration par défaut.
Sortir du monde purement en mémoire
Jusqu'ici, tout ce que manipulait votre programme disparaissait à sa fermeture. Lire et écrire des fichiers permet de faire persister des données, et de communiquer avec le système de fichiers ou d'autres programmes via des formats comme JSON. Mais ouvrir une ressource externe introduit un risque nouveau : et si le fichier n'existe pas, si le disque est plein, si le réseau tombe en cours de route ?
Pourquoi le with est presque toujours obligatoire
L'instruction with garantit qu'une ressource (fichier, connexion...) sera correctement libérée même si une erreur survient au milieu du traitement. Sans elle, il faudrait explicitement fermer chaque fichier dans un bloc finally, ce qui est facile à oublier. C'est le premier exemple concret d'un context manager, un concept qu'une leçon ultérieure explorera en détail.
Bonne pratique
N'ouvrez jamais un fichier avec f = open(...) sans with : si une exception survient entre l'ouverture et f.close(), le fichier reste ouvert jusqu'à ce que le garbage collector s'en charge, ce qui peut bloquer d'autres processus qui tentent d'y accéder.
Les exceptions : signaler qu'un problème a eu lieu
Une exception est un mécanisme qui interrompt le déroulement normal d'un programme pour signaler qu'une situation anormale s'est produite. Plutôt que de vérifier manuellement chaque condition d'erreur possible avant chaque opération, on tente l'opération et on gère l'échec s'il survient : c'est la philosophie il est plus facile de demander pardon que la permission, très ancrée en Python.
Bien construire son bloc try
| Clause | S'exécute quand... |
|---|---|
try | Toujours, en premier |
except | Une exception du type indiqué a été levée dans le try |
else | Aucune exception n'a été levée dans le try |
finally | Systématiquement, qu'il y ait eu erreur ou non |
Chaque clause a un rôle précis : except capture une erreur spécifique, else s'exécute uniquement si aucune erreur n'a eu lieu, et finally s'exécute systématiquement, erreur ou pas. Chaîner les exceptions avec raise ... from ... permet de préserver la cause originale d'un problème, ce qui est précieux pour déboguer un système en couches.
Commandes & code
Fichiers et gestion des exceptions
Lire/écrire sur disque de façon sûre, et gérer les erreurs proprement.
# Ecriture dans un fichier -- le "with" garantit la fermeture meme en cas d'erreur
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("Premiere ligne\n")
f.writelines(["Deuxieme ligne\n", "Troisieme ligne\n"])
# Lecture complete
with open("notes.txt", "r", encoding="utf-8") as f:
contenu = f.read()
# Lecture ligne par ligne (efficace en memoire pour de gros fichiers)
with open("notes.txt", "r", encoding="utf-8") as f:
for ligne in f:
print(ligne.strip())
# Mode append : ajouter sans ecraser
with open("notes.txt", "a", encoding="utf-8") as f:
f.write("Ligne ajoutee\n")
# Manipulation de chemins portable avec pathlib (recommande depuis Python 3.4+)
from pathlib import Path
chemin = Path("data") / "fichiers" / "notes.txt"
print(chemin.exists(), chemin.suffix, chemin.stem, chemin.parent)
chemin.parent.mkdir(parents=True, exist_ok=True)
# JSON : serialisation/deserialisation
import json
donnees = {"nom": "Alice", "scores": [10, 20, 30]}
with open("data.json", "w", encoding="utf-8") as f:
json.dump(donnees, f, indent=2, ensure_ascii=False)
with open("data.json", "r", encoding="utf-8") as f:
relu = json.load(f)
# --- Exceptions ---
def diviser(a, b):
try:
resultat = a / b
except ZeroDivisionError:
print("Division par zero interdite")
return None
except TypeError as e:
print(f"Type invalide : {e}")
return None
else:
# execute SEULEMENT si aucune exception n'a ete levee
print("Division reussie")
return resultat
finally:
# execute TOUJOURS, meme en cas de return dans try/except
print("Nettoyage effectue")
diviser(10, 2)
diviser(10, 0)
# Capturer plusieurs exceptions en une clause
try:
fichier = open("inexistant.txt")
except (FileNotFoundError, PermissionError) as e:
print(f"Erreur d'acces : {e}")
# Lever ses propres exceptions
class SoldeInsuffisantError(Exception):
"""Levee quand un retrait depasse le solde disponible."""
def __init__(self, solde, montant):
self.solde = solde
self.montant = montant
super().__init__(f"Solde {solde} insuffisant pour retirer {montant}")
def retirer(solde, montant):
if montant > solde:
raise SoldeInsuffisantError(solde, montant)
return solde - montant
try:
retirer(100, 150)
except SoldeInsuffisantError as e:
print(f"Erreur metier : {e}")
# Chainage d'exceptions : preserver la cause originale (raise ... from ...)
def charger_config(chemin):
try:
with open(chemin) as f:
return json.load(f)
except json.JSONDecodeError as e:
raise ValueError(f"Configuration invalide : {chemin}") from e
# Exception groups (Python 3.11+) : plusieurs erreurs a la fois
try:
raise ExceptionGroup("plusieurs erreurs", [ValueError("v1"), TypeError("t1")])
except* ValueError as eg:
print(f"Erreurs de valeur : {eg.exceptions}")
except* TypeError as eg:
print(f"Erreurs de type : {eg.exceptions}")Résumé
withgarantit la fermeture des ressources même en cas d'exception (context manager).pathlib.Pathest préférable à la manipulation de chaînes pour les chemins de fichiers.raise ... from ...préserve la cause originale d'une exception pour le débogage.except*(3.11+) gère lesExceptionGrouppour traiter plusieurs erreurs simultanées.
Exercices pratiques
Mission : donner un vrai message d'erreur à une config cassée
Objectif : Sécuriser le chargement d'un fichier de configuration JSON avec une gestion d'exceptions précise, et préserver la cause d'origine d'une erreur.
Contexte
Une application appelle charger_config("config.json") au démarrage. Le jour où le fichier est absent, ou contient une virgule en trop rendant le JSON invalide, l'utilisateur final voit une trace Python brute et incompréhensible s'afficher dans le terminal au lieu d'un message clair.
Tu dois intercepter précisément les erreurs possibles, garantir la fermeture du fichier dans tous les cas, et faire remonter une erreur métier claire sans perdre la cause technique d'origine.