36  Lire et écrire des fichiers

Un vrai programme communique avec le monde extérieur : lire une configuration, enregistrer des résultats, importer des données utilisateurs. Python offre une API très simple pour travailler avec les fichiers — mais elle cache quelques pièges classiques que le TOSA teste régulièrement.

36.1 Ouvrir un fichier : open()

La fonction native open() renvoie un objet fichier qu’on peut lire, écrire ou fermer.

fichier = open(chemin, mode, encoding="utf-8")

Les modes d’ouverture

Mode Effet Si le fichier n’existe pas
"r" Lecture seule (défaut) Erreur FileNotFoundError
"w" Écriture (écrase !) Créé
"a" Ajout (append) en fin Créé
"x" Création exclusive Créé
"r+" Lecture + écriture Erreur
"rb", "wb" Lecture/écriture en binaire
⚠️ Piège TOSA : "w" écrase le contenu existant !

Ouvrir en mode "w" vide le fichier avant d’écrire. Si vous voulez ajouter à la fin, utilisez "a".

# ❌ Écrase le fichier s'il existe
f = open("log.txt", "w")

# ✅ Ajoute à la fin
f = open("log.txt", "a")

L’encodage

Spécifiez toujours encoding="utf-8" pour les fichiers texte — c’est la norme universelle pour gérer les accents, caractères spéciaux, etc.

open("fichier.txt", "r", encoding="utf-8")

Sans cet argument, Python utilise l’encodage système par défaut, ce qui crée des bugs d’affichage selon les plateformes (notamment avec les accents sur Windows).

36.2 Lire un fichier

Créons d’abord un fichier de test, puis lisons-le.

# Créer un fichier de test
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("Alice 14\nBob 8\nCharlie 17\nEve 15\n")

print("Fichier créé.")
Fichier créé.

Lire tout d’un coup : read()

Renvoie toute la contenu du fichier comme une chaîne.

with open("notes.txt", "r", encoding="utf-8") as f:
    contenu = f.read()

print(repr(contenu))
'Alice 14\nBob 8\nCharlie 17\nEve 15\n'

Lire ligne par ligne : boucle sur le fichier

La méthode recommandée : itérer directement sur le fichier.

with open("notes.txt", "r", encoding="utf-8") as f:
    for ligne in f:
        print(repr(ligne))
'Alice 14\n'
'Bob 8\n'
'Charlie 17\n'
'Eve 15\n'
⚠️ Le \n à la fin de chaque ligne

Quand vous lisez ligne par ligne, chaque ligne conserve son \n de fin. Pour l’enlever, utilisez .rstrip() ou .strip() :

with open("notes.txt", "r", encoding="utf-8") as f:
    for ligne in f:
        ligne = ligne.rstrip("\n")    # enlève le \n final
        print(repr(ligne))
'Alice 14'
'Bob 8'
'Charlie 17'
'Eve 15'

Obtenir toutes les lignes en liste : readlines()

with open("notes.txt", "r", encoding="utf-8") as f:
    lignes = f.readlines()

print(lignes)
['Alice 14\n', 'Bob 8\n', 'Charlie 17\n', 'Eve 15\n']

Attention : readlines() charge toutes les lignes en mémoire. Pour un gros fichier, préférez itérer.

Récapitulatif des méthodes de lecture

Méthode Renvoie Usage
f.read() Chaîne complète Petits fichiers
f.read(n) n caractères Lecture contrôlée
f.readline() Une ligne Lecture manuelle
f.readlines() Liste de toutes les lignes Lecture complète
for ligne in f: Itérateur ligne par ligne Recommandé — efficace

36.3 Écrire dans un fichier

write() — écrire une chaîne

with open("rapport.txt", "w", encoding="utf-8") as f:
    f.write("Rapport du jour\n")
    f.write("================\n")
    f.write("Ventes : 1250 €\n")
    f.write("Clients : 15\n")

# Vérifier le résultat
with open("rapport.txt", "r", encoding="utf-8") as f:
    print(f.read())
Rapport du jour
================
Ventes : 1250 €
Clients : 15
⚠️ write() n’ajoute PAS de \n automatique

Contrairement à print, write n’ajoute rien à la fin. Oubliez le \n et tout sera sur la même ligne :

# ❌ Tout sur une ligne
f.write("Alice")
f.write("Bob")
# → "AliceBob"

# ✅ Ajoutez \n manuellement
f.write("Alice\n")
f.write("Bob\n")

writelines() — écrire plusieurs lignes

Prend une séquence de chaînes. Attention : n’ajoute pas de \n entre elles non plus !

lignes = ["Alice 14\n", "Bob 8\n", "Charlie 17\n"]

with open("notes.txt", "w", encoding="utf-8") as f:
    f.writelines(lignes)

with open("notes.txt", "r", encoding="utf-8") as f:
    print(f.read())
Alice 14
Bob 8
Charlie 17

Mode ajout "a"

Pour ajouter à la fin sans écraser :

with open("rapport.txt", "a", encoding="utf-8") as f:
    f.write("\nMise à jour 14h30\n")
    f.write("Ventes : 1400 €\n")

with open("rapport.txt", "r", encoding="utf-8") as f:
    print(f.read())
Rapport du jour
================
Ventes : 1250 €
Clients : 15

Mise à jour 14h30
Ventes : 1400 €

36.4 Le context manager with

Vous l’avez vu dans tous les exemples. Le with garantit que le fichier est automatiquement fermé, même en cas d’erreur.

Sans with (à éviter)

# ❌ Approche verbeuse et risquée
f = open("fichier.txt", "r", encoding="utf-8")
try:
    contenu = f.read()
finally:
    f.close()

Avec with (recommandé)

# ✅ Concis et sûr
with open("fichier.txt", "r", encoding="utf-8") as f:
    contenu = f.read()
# f est automatiquement fermé à la sortie du bloc
Pourquoi toujours utiliser with ?
  • Sécurité : le fichier est fermé même si une exception survient.
  • Lisibilité : on voit clairement le périmètre d’utilisation du fichier.
  • Concision : pas besoin de try/finally ni de close().
  • Idiomatique : c’est la manière Pythonique.

Utilisez with systématiquement pour les fichiers.

36.5 Gérer les erreurs courantes

Les fichiers peuvent ne pas exister, ne pas être accessibles… Combinez avec try/except :

def lire_fichier(chemin):
    try:
        with open(chemin, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        print(f"Fichier introuvable : {chemin}")
        return None
    except PermissionError:
        print(f"Accès refusé : {chemin}")
        return None

contenu = lire_fichier("n_existe_pas.txt")
print(contenu)
Fichier introuvable : n_existe_pas.txt
None

36.6 Le format JSON

JSON (JavaScript Object Notation) est le format universel pour échanger des données structurées. Python lit et écrit du JSON avec le module json de la bibliothèque standard.

Correspondance JSON ↔︎ Python

JSON Python
object {...} dict
array [...] list
string str
number (entier) int
number (flottant) float
true, false True, False
null None

Écrire du JSON : json.dump()

import json

donnees = {
    "nom": "Alice",
    "age": 30,
    "notes": [14, 15, 12],
    "admin": True,
}

with open("data.json", "w", encoding="utf-8") as f:
    json.dump(donnees, f, ensure_ascii=False, indent=2)

# Vérifier
with open("data.json", "r", encoding="utf-8") as f:
    print(f.read())
{
  "nom": "Alice",
  "age": 30,
  "notes": [
    14,
    15,
    12
  ],
  "admin": true
}

Options utiles :

  • indent=2 : indente le JSON pour le rendre lisible.
  • ensure_ascii=False : garde les accents (sans ça, é devient \u00e9).
  • sort_keys=True : trie les clés alphabétiquement.

Lire du JSON : json.load()

import json

with open("data.json", "r", encoding="utf-8") as f:
    donnees = json.load(f)

print(donnees)
print(type(donnees))
print(donnees["notes"])
{'nom': 'Alice', 'age': 30, 'notes': [14, 15, 12], 'admin': True}
<class 'dict'>
[14, 15, 12]

Sérialiser/désérialiser depuis/vers une chaîne

Si vous travaillez avec du JSON en mémoire (sans fichier) :

  • json.dumps(obj) (dump string) : Python → chaîne JSON.
  • json.loads(chaine) (load string) : chaîne JSON → Python.
import json

d = {"a": 1, "liste": [1, 2, 3]}

chaine = json.dumps(d)
print(chaine)
print(type(chaine))

reconstitue = json.loads(chaine)
print(reconstitue)
{"a": 1, "liste": [1, 2, 3]}
<class 'str'>
{'a': 1, 'liste': [1, 2, 3]}

Moyen mnémotechnique :

  • dump / load : fichier (on donne un objet file).
  • dumps / loads : string (on donne une chaîne).

Limitations du JSON

JSON ne sait pas sérialiser :

  • Les tuples (convertis en listes).
  • Les sets (erreur).
  • Les objets personnalisés (sans configuration).
  • Les dates (il faut les convertir en chaîne).
import json
json.dumps({1, 2, 3})      # set non supporté
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[13], line 2
      1 import json
----> 2 json.dumps({1, 2, 3})      # set non supporté

File ~\AppData\Local\Programs\Python\Python312\Lib\json\__init__.py:231, in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
    226 # cached encoder
    227 if (not skipkeys and ensure_ascii and
    228     check_circular and allow_nan and
    229     cls is None and indent is None and separators is None and
    230     default is None and not sort_keys and not kw):
--> 231     return _default_encoder.encode(obj)
    232 if cls is None:
    233     cls = JSONEncoder

File ~\AppData\Local\Programs\Python\Python312\Lib\json\encoder.py:200, in JSONEncoder.encode(self, o)
    196         return encode_basestring(o)
    197 # This doesn't pass the iterator directly to ''.join() because the
    198 # exceptions aren't as detailed.  The list call should be roughly
    199 # equivalent to the PySequence_Fast that ''.join() would do.
--> 200 chunks = self.iterencode(o, _one_shot=True)
    201 if not isinstance(chunks, (list, tuple)):
    202     chunks = list(chunks)

File ~\AppData\Local\Programs\Python\Python312\Lib\json\encoder.py:258, in JSONEncoder.iterencode(self, o, _one_shot)
    253 else:
    254     _iterencode = _make_iterencode(
    255         markers, self.default, _encoder, self.indent, floatstr,
    256         self.key_separator, self.item_separator, self.sort_keys,
    257         self.skipkeys, _one_shot)
--> 258 return _iterencode(o, 0)

File ~\AppData\Local\Programs\Python\Python312\Lib\json\encoder.py:180, in JSONEncoder.default(self, o)
    161 def default(self, o):
    162     """Implement this method in a subclass such that it returns
    163     a serializable object for ``o``, or calls the base implementation
    164     (to raise a ``TypeError``).
   (...)    178 
    179     """
--> 180     raise TypeError(f'Object of type {o.__class__.__name__} '
    181                     f'is not JSON serializable')

TypeError: Object of type set is not JSON serializable

Pour ces cas, utilisez pickle (sérialisation binaire Python) ou convertissez avant.

36.7 Le format CSV

CSV (Comma-Separated Values) est le format tabulaire universel, lisible par Excel, tableurs, bases de données. Python fournit le module csv.

Écrire un fichier CSV

import csv

donnees = [
    ["nom", "age", "ville"],
    ["Alice", 30, "Paris"],
    ["Bob", 25, "Lyon"],
    ["Charlie", 35, "Marseille"],
]

with open("personnes.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerows(donnees)

# Vérifier
with open("personnes.csv", "r", encoding="utf-8") as f:
    print(f.read())
nom,age,ville
Alice,30,Paris
Bob,25,Lyon
Charlie,35,Marseille
⚠️ newline="" obligatoire !

Le paramètre newline="" dans open() est indispensable en écriture CSV. Sans lui, Windows produit des doubles retours à la ligne (bug silencieux).

Règle : toujours newline="" pour les CSV.

Lire un fichier CSV

import csv

with open("personnes.csv", "r", encoding="utf-8") as f:
    reader = csv.reader(f)
    for ligne in reader:
        print(ligne)
['nom', 'age', 'ville']
['Alice', '30', 'Paris']
['Bob', '25', 'Lyon']
['Charlie', '35', 'Marseille']

Chaque ligne est une liste de chaînes. Les nombres sont lus comme des chaînes — il faut convertir si besoin :

import csv

with open("personnes.csv", "r", encoding="utf-8") as f:
    reader = csv.reader(f)
    header = next(reader)             # récupérer l'en-tête
    for ligne in reader:
        nom, age, ville = ligne
        age = int(age)                 # conversion
        print(f"{nom} ({age}) vit à {ville}")
Alice (30) vit à Paris
Bob (25) vit à Lyon
Charlie (35) vit à Marseille

Lire comme des dictionnaires : DictReader

Plus pratique pour manipuler les données par nom de colonne :

import csv

with open("personnes.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for ligne in reader:
        print(f"{ligne['nom']}{ligne['ville']}")
Alice → Paris
Bob → Lyon
Charlie → Marseille

DictReader utilise la première ligne comme en-tête pour nommer les colonnes.

Écrire comme dictionnaires : DictWriter

import csv

donnees = [
    {"nom": "Alice", "age": 30, "ville": "Paris"},
    {"nom": "Bob", "age": 25, "ville": "Lyon"},
]

with open("sortie.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["nom", "age", "ville"])
    writer.writeheader()
    writer.writerows(donnees)

with open("sortie.csv", "r", encoding="utf-8") as f:
    print(f.read())
nom,age,ville
Alice,30,Paris
Bob,25,Lyon

Séparateurs et dialectes

En France, on utilise souvent ; au lieu de , (car la virgule est utilisée comme séparateur décimal). On précise avec delimiter=";" :

import csv

# Écrire en CSV « français »
with open("fr.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f, delimiter=";")
    writer.writerow(["produit", "prix"])
    writer.writerow(["pain", "1,20"])
    writer.writerow(["lait", "0,95"])

with open("fr.csv", "r", encoding="utf-8") as f:
    print(f.read())
produit;prix
pain;1,20
lait;0,95

36.8 Les fichiers binaires (aperçu)

Pour les images, PDF, ZIP… il faut lire en mode binaire ("rb" / "wb").

# Copier un fichier binaire
with open("source.png", "rb") as src:
    contenu = src.read()

with open("destination.png", "wb") as dst:
    dst.write(contenu)

Le mode binaire renvoie des bytes (b"...") au lieu de strings. Ce n’est généralement pas utile au niveau Avancé du TOSA, mais à reconnaître.

36.9 pathlib : chemins modernes

Le module pathlib offre une API objet pour les chemins de fichiers. Plus lisible que os.path.

from pathlib import Path

p = Path("notes.txt")

# Informations
print(f"Existe ? : {p.exists()}")
print(f"Est un fichier ? : {p.is_file()}")
print(f"Taille : {p.stat().st_size} octets")
print(f"Nom : {p.name}")
print(f"Extension : {p.suffix}")

# Lire/écrire sans open() — pratique pour les petits fichiers
contenu = p.read_text(encoding="utf-8")
print(f"Début : {contenu[:50]}...")
Existe ? : True
Est un fichier ? : True
Taille : 29 octets
Nom : notes.txt
Extension : .txt
Début : Alice 14
Bob 8
Charlie 17
...

pathlib permet aussi la concaténation avec / :

from pathlib import Path

dossier = Path("/home/user/documents")
fichier = dossier / "rapport.txt"
# fichier vaut Path("/home/user/documents/rapport.txt")

On approfondira pathlib en Partie 4.

36.10 Nettoyer : supprimer les fichiers de démo

Nettoyage final des fichiers temporaires créés pour les démos :

from pathlib import Path

for nom in ["notes.txt", "rapport.txt", "data.json", "personnes.csv", "sortie.csv", "fr.csv"]:
    p = Path(nom)
    if p.exists():
        p.unlink()        # suppression

print("Nettoyage terminé.")
Nettoyage terminé.

🧩 Quiz 7.1 — Fichiers

Question 1

Que fait open("log.txt", "w") si le fichier existe déjà ?

  1. Lève une erreur
  2. Ouvre en mode lecture
  3. Vide le fichier et prépare l’écriture
  4. Ajoute à la fin

c) — le mode "w" écrase le contenu existant. Pour ajouter sans perdre les données, utilisez "a" (append).

Question 2

Pourquoi utiliser with open(...) as f: plutôt que f = open(...) ?

  1. C’est obligatoire en Python 3
  2. Cela ferme automatiquement le fichier, même en cas d’erreur
  3. C’est plus rapide
  4. Cela fonctionne aussi sur les URLs

b) — le context manager garantit la fermeture automatique, y compris si une exception survient. C’est la manière moderne et sûre.

Question 3

Quelle est la bonne approche pour parcourir un gros fichier texte ?

  1. f.read() puis split("\n")
  2. f.readlines() puis boucle
  3. Boucle directe for ligne in f:
  4. f.read(99999999)

c) — la boucle directe n’utilise pas de mémoire supplémentaire (lit ligne par ligne). read() et readlines() chargent tout en mémoire.

Question 4

Que renvoie json.dumps({"a": 1}) ?

  1. Un dict Python
  2. Une chaîne JSON '{"a": 1}'
  3. Un fichier JSON
  4. Une erreur

b)dumps (string) renvoie une chaîne. Ne pas confondre avec dump (fichier).

import json
print(json.dumps({"a": 1}))
print(type(json.dumps({"a": 1})))
{"a": 1}
<class 'str'>

Question 5

Pourquoi faut-il newline="" avec un fichier CSV ?

  1. Pour enlever le retour à la ligne automatique
  2. Pour éviter les doubles sauts de ligne sur Windows
  3. Pour accepter les caractères spéciaux
  4. Ce n’est pas nécessaire

b) — sans newline="", le module csv et Windows ajoutent chacun un \n, produisant des doubles sauts de ligne. Règle : toujours newline="" en écriture CSV.

Question 6

Quelle est la bonne correspondance ?

  1. json.load → chaîne JSON vers objet Python
  2. json.loads → fichier JSON vers objet Python
  3. json.dump → objet Python vers fichier JSON
  4. json.dumps → objet Python vers fichier JSON

c)dump écrit dans un fichier, dumps renvoie une string. Moyen mnémo : le s final, c’est « string ».

Vers objet Depuis objet
Fichier json.load(f) json.dump(obj, f)
Chaîne json.loads(s) json.dumps(obj)

Question 7

Quelle exception est levée par open("absent.txt", "r") si le fichier n’existe pas ?

  1. ValueError
  2. NameError
  3. FileNotFoundError
  4. PermissionError

c) FileNotFoundError — sous-classe d’OSError. À rattraper dans un try/except pour traiter ce cas proprement.


✏️ Exercice 7.1 — Lecture + statistiques

Créez un fichier notes.txt contenant une note par ligne, puis écrivez une fonction statistiques(chemin) qui renvoie un dict avec min, max, moyenne, nb.

def statistiques(chemin):
    ...

# Créer un fichier de test
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("12\n15\n9\n17\n11\n")

print(statistiques("notes.txt"))
def statistiques(chemin):
    try:
        with open(chemin, "r", encoding="utf-8") as f:
            notes = [int(ligne.strip()) for ligne in f if ligne.strip()]
    except FileNotFoundError:
        return None

    if not notes:
        return {"nb": 0, "min": None, "max": None, "moyenne": None}

    return {
        "nb": len(notes),
        "min": min(notes),
        "max": max(notes),
        "moyenne": round(sum(notes) / len(notes), 2),
    }


# Créer un fichier de test
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("12\n15\n9\n17\n11\n")

print(statistiques("notes.txt"))
print(statistiques("n_existe_pas.txt"))

# Nettoyage
from pathlib import Path
Path("notes.txt").unlink()
{'nb': 5, 'min': 9, 'max': 17, 'moyenne': 12.8}
None
  • Compréhension de liste : [int(ligne.strip()) for ligne in f] — lecture + conversion en une ligne.
  • if ligne.strip()ignorer les lignes vides.
  • try/except pour le cas où le fichier n’existe pas.

✏️ Exercice 7.2 — Sauvegarde et rechargement JSON

Créez deux fonctions :

  1. sauver(catalogue, chemin) : écrit une liste de dicts en JSON.
  2. charger(chemin) : relit le fichier et renvoie la liste.

Testez avec un catalogue de livres.

import json

def sauver(catalogue, chemin):
    ...

def charger(chemin):
    ...

catalogue = [
    {"titre": "Dune", "auteur": "Herbert", "annee": 1965},
    {"titre": "1984", "auteur": "Orwell", "annee": 1949},
]

sauver(catalogue, "catalogue.json")
loaded = charger("catalogue.json")
print(loaded)
import json
from pathlib import Path

def sauver(catalogue, chemin):
    with open(chemin, "w", encoding="utf-8") as f:
        json.dump(catalogue, f, ensure_ascii=False, indent=2)

def charger(chemin):
    try:
        with open(chemin, "r", encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        return []


catalogue = [
    {"titre": "Dune", "auteur": "Herbert", "annee": 1965},
    {"titre": "1984", "auteur": "Orwell", "annee": 1949},
    {"titre": "L'Étranger", "auteur": "Camus", "annee": 1942},
]

sauver(catalogue, "catalogue.json")

# Vérifier le contenu du fichier
with open("catalogue.json", "r", encoding="utf-8") as f:
    print(f.read())

# Recharger
loaded = charger("catalogue.json")
print("\nDonnées rechargées :")
for livre in loaded:
    print(f"  - {livre['titre']} ({livre['annee']})")

# Nettoyage
Path("catalogue.json").unlink()
[
  {
    "titre": "Dune",
    "auteur": "Herbert",
    "annee": 1965
  },
  {
    "titre": "1984",
    "auteur": "Orwell",
    "annee": 1949
  },
  {
    "titre": "L'Étranger",
    "auteur": "Camus",
    "annee": 1942
  }
]

Données rechargées :
  - Dune (1965)
  - 1984 (1949)
  - L'Étranger (1942)

✏️ Exercice 7.3 — Export CSV depuis un dict de dicts

Soit un dictionnaire d’étudiants. Exportez-le en CSV avec les colonnes nom, moyenne, mention.

import csv

etudiants = {
    "Alice":   {"moyenne": 14.5, "mention": "Bien"},
    "Bob":     {"moyenne": 9.8,  "mention": "Insuffisant"},
    "Charlie": {"moyenne": 17.0, "mention": "Très bien"},
}

def exporter_csv(etudiants, chemin):
    ...

exporter_csv(etudiants, "classe.csv")
import csv
from pathlib import Path

etudiants = {
    "Alice":   {"moyenne": 14.5, "mention": "Bien"},
    "Bob":     {"moyenne": 9.8,  "mention": "Insuffisant"},
    "Charlie": {"moyenne": 17.0, "mention": "Très bien"},
    "Diana":   {"moyenne": 12.3, "mention": "Assez bien"},
}

def exporter_csv(etudiants, chemin):
    with open(chemin, "w", encoding="utf-8", newline="") as f:
        writer = csv.writer(f, delimiter=";")
        writer.writerow(["nom", "moyenne", "mention"])
        for nom, infos in etudiants.items():
            writer.writerow([nom, infos["moyenne"], infos["mention"]])


exporter_csv(etudiants, "classe.csv")

# Relire et afficher
with open("classe.csv", "r", encoding="utf-8") as f:
    print(f.read())

# Nettoyage
Path("classe.csv").unlink()
nom;moyenne;mention
Alice;14.5;Bien
Bob;9.8;Insuffisant
Charlie;17.0;Très bien
Diana;12.3;Assez bien
import csv
from pathlib import Path

etudiants = {
    "Alice":   {"moyenne": 14.5, "mention": "Bien"},
    "Bob":     {"moyenne": 9.8,  "mention": "Insuffisant"},
}

def exporter_csv_v2(etudiants, chemin):
    # Construire une liste de dicts plats
    lignes = [{"nom": nom, **infos} for nom, infos in etudiants.items()]

    with open(chemin, "w", encoding="utf-8", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=["nom", "moyenne", "mention"], delimiter=";")
        writer.writeheader()
        writer.writerows(lignes)


exporter_csv_v2(etudiants, "classe2.csv")

with open("classe2.csv", "r", encoding="utf-8") as f:
    print(f.read())

Path("classe2.csv").unlink()
nom;moyenne;mention
Alice;14.5;Bien
Bob;9.8;Insuffisant

✏️ Exercice 7.4 — Journal d’événements

Créez une fonction logger(message, fichier="log.txt") qui ajoute à la fin du fichier une ligne au format "[2026-04-22 14:30:00] message".

from datetime import datetime

def logger(message, fichier="log.txt"):
    ...

logger("Démarrage de l'application")
logger("Utilisateur connecté : Alice")
logger("Erreur : fichier introuvable")
from datetime import datetime
from pathlib import Path

def logger(message, fichier="log.txt"):
    horodatage = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(fichier, "a", encoding="utf-8") as f:
        f.write(f"[{horodatage}] {message}\n")


# Écrire quelques lignes
logger("Démarrage de l'application")
logger("Utilisateur connecté : Alice")
logger("Erreur : fichier introuvable")

# Afficher le log
with open("log.txt", "r", encoding="utf-8") as f:
    print(f.read())

# Nettoyage
Path("log.txt").unlink()
[2026-04-22 14:28:45] Démarrage de l'application
[2026-04-22 14:28:45] Utilisateur connecté : Alice
[2026-04-22 14:28:45] Erreur : fichier introuvable
  • Mode "a" (append) : ajoute à la fin, ne remplace jamais.
  • datetime.now().strftime(...) : formater la date courante.
  • Pattern courant pour du logging simple (en production, utiliser le module logging).

À retenir

Points clés du chapitre
  1. with open(...) as f: est la manière standard d’ouvrir un fichier. Fermeture automatique garantie.
  2. Modes : "r" (lecture), "w" (écriture — écrase !), "a" (ajout), "x" (création exclusive).
  3. Toujours préciser encoding="utf-8" pour du texte.
  4. Lecture efficace : for ligne in f: (pas readlines() sur de gros fichiers).
  5. write n’ajoute pas de \n automatique — pensez-y.
  6. JSON : json.dump / json.load pour fichier, json.dumps / json.loads pour chaîne.
  7. CSV : csv.reader / csv.writer ou DictReader / DictWriter. newline="" obligatoire.
  8. pathlib.Path offre une API objet moderne pour les chemins.
  9. try/except FileNotFoundError pour gérer les fichiers absents.

← Chapitre précédent : ExceptionsChapitre suivant : pip et l’écosystème →