Retour au cours

backend / python

Fichiers et gestion des exceptions

Leçon 101 exercice

Explication

Ce que vous allez apprendre

  • Lire et écrire un fichier texte ou JSON en toute sécurité avec with open(...)
  • Comprendre pourquoi with garantit la fermeture d'une ressource même en cas d'erreur
  • Distinguer les rôles précis de try, except, else et finally
  • 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

ClauseS'exécute quand...
tryToujours, en premier
exceptUne exception du type indiqué a été levée dans le try
elseAucune exception n'a été levée dans le try
finallySysté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.

python
# 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é

  • with garantit la fermeture des ressources même en cas d'exception (context manager).
  • pathlib.Path est 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 les ExceptionGroup pour traiter plusieurs erreurs simultanées.

Exercices pratiques

1 disponible
1

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.

Résoudre l’exercice →