Semaine 5 : Lire et écrire des fichiers CSV
🎯 Objectifs d'apprentissage
À la fin de cette semaine, tu seras capable de :
- Ouvrir et fermer un fichier en toute sécurité avec
with, et expliquer pourquoi cela compte. - Lire les lignes d'un fichier CSV en Python avec le module intégré
csv, aussi bien sous forme de listes que de dictionnaires. - Écrire des lignes simples et des lignes sous forme de dictionnaires dans un nouveau fichier CSV.
- Combiner listes, dictionnaires, boucles et fonctions pour résumer un jeu de données réel.
- Écrire un petit mini-projet de bout en bout, sans aide, en utilisant uniquement ce que tu as appris aux semaines 1 à 4.
Leçon
Ouvrir des fichiers en toute sécurité avec with
Avant d'aborder les CSV spécifiquement, une nouvelle syntaxe : with open(...) as f: ouvre un fichier, te donne accès à un handle vers celui-ci sous le nom f, et le referme automatiquement une fois le bloc indenté terminé — même si une erreur survient en cours de route. Cela compte parce qu'un fichier non fermé peut perdre des écritures mises en mémoire tampon ou verrouiller le fichier pour d'autres programmes. L'alternative, f = open(...) suivi d'un f.close() manuel à la fin, est facile à oublier (surtout si une erreur provoque une sortie prématurée) ; with fait de « toujours le fermer » le comportement par défaut, que tu n'as pas besoin de te rappeler.
CSV : des lignes de valeurs séparées par des virgules
Un fichier CSV (comma-separated values, valeurs séparées par des virgules) est un tableau en texte brut — une ligne par enregistrement, les valeurs séparées par des virgules. Le jeu de données de cette semaine, students-normal.csv, a une ligne d'en-tête (name,quiz1,quiz2,quiz3) suivie d'une ligne par étudiant.
Le bac à sable du bouton flottant (FAB) a déjà students-normal.csv préchargé — pas besoin de copier-coller quoi que ce soit. open("students-normal.csv") dans votre code ci-dessous le trouvera directement.
import csv
with open("students-normal.csv", newline="") as f:
reader = csv.reader(f)
header = next(reader) # first row: column names
for row in reader:
print(row) # each row is a list of strings
Chaque valeur lue de cette manière est un str — un nombre comme "87" dans le fichier arrive sous forme de chaîne "87", pas de l'entier 87. Tu dois la convertir toi-même, exactement comme input() à la semaine 1. next(reader) extrait uniquement la première ligne (l'en-tête) du lecteur avant que la boucle for ne commence, de sorte que la boucle elle-même ne voit que les lignes de données réelles. L'argument newline="" empêche la gestion propre des fins de ligne du module csv d'entrer en conflit avec celle de Python — c'est du code standard que tu devrais toujours inclure lorsque tu ouvres un fichier pour csv.reader/csv.writer, même si en expliquer précisément la raison dépasse le cadre de cette semaine.
csv.DictReader : des lignes sous forme de dictionnaires
Plutôt que de suivre les positions des colonnes par index, csv.DictReader te donne chaque ligne sous forme de dict indexé par les noms d'en-tête — c'est généralement l'option la plus lisible, et elle lit automatiquement la ligne d'en-tête pour toi (pas besoin de next(reader) manuel) :
with open("students-normal.csv", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
name = row["name"]
score = int(row["quiz1"]) # still a str until you convert it
print(name, score)
Compare ceci à csv.reader : avec csv.reader, row[1] signifie « ce qui se trouve dans la colonne 1 » — fragile si l'ordre des colonnes change un jour. Avec csv.DictReader, row["quiz1"] dit exactement ce que tu veux dire, quel que soit l'ordre des colonnes, au petit coût d'une recherche dans un dictionnaire plutôt que d'un index de liste.
Écrire des fichiers CSV
csv.writer et csv.DictWriter sont l'image miroir de csv.reader/csv.DictReader — utiles pour enregistrer dans un fichier un résumé que tu as calculé :
with open("summary.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["name", "average"])
writer.writerow(["Amina", 91.5])
Le "w" (mode écriture) est important — open("summary.csv", "w") crée le fichier s'il n'existe pas, et écrase complètement son contenu s'il existe déjà. csv.DictWriter reflète DictReader : tu écris des dictionnaires au lieu de simples listes, et il a besoin de connaître les noms de colonnes (fieldnames) à l'avance pour savoir dans quel ordre les écrire et pouvoir produire une ligne d'en-tête correspondante :
with open("summary.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "average"])
writer.writeheader() # writes the header row
writer.writerow({"name": "Amina", "average": 91.5})
writer.writerow({"name": "Youssef", "average": 74.3})
DictWriter est le pendant naturel si tu as construit ton résumé sous forme de liste de dictionnaires (la forme « imbrication de collections » de la semaine 3) plutôt que sous forme d'un simple dictionnaire nom → moyenne.
Mettre le tout ensemble : un mini-projet
Le projet de cette semaine combine tout ce qui vient des semaines 1 à 4 : lire un CSV de notes d'étudiants, calculer la moyenne de chacun avec une fonction, la stocker dans un dict, et afficher un résumé trié du plus haut au plus bas — en utilisant sorted() avec une fonction key, qui elle-même prend une fonction en argument, exactement comme compose dans la question socratique de la semaine dernière :
def average(scores):
return sum(scores) / len(scores)
averages = {} # name -> average
with open("students-normal.csv", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
name = row["name"]
scores = [int(row["quiz1"]), int(row["quiz2"]), int(row["quiz3"])]
averages[name] = average(scores)
for name in sorted(averages, key=lambda n: averages[n], reverse=True):
print(name, round(averages[name], 1))
lambda n: averages[n] est une petite fonction sans nom — lis-la comme « la règle qui associe un nom à sa moyenne », utilisée uniquement pour indiquer à sorted() selon quoi trier. Une lambda est exactement équivalente à un petit def (lambda n: averages[n] se comporte comme la fonction d'une ligne def key_fn(n): return averages[n]) ; elle existe uniquement pour que tu n'aies pas besoin de nommer et de définir une fonction séparée juste pour donner à sorted() une règle simple et jetable.
⚠️ Pièges courants
- Oublier
newline="". Sans cela,csv.writerpeut insérer des lignes vides supplémentaires entre les enregistrements sur certains systèmes (notamment Windows) — inclus-le toujours lors de la lecture ou de l'écriture de fichiers CSV. - Ouvrir en mode
"w"par accident alors que tu voulais ajouter des données.open("summary.csv", "w")efface d'abord le contenu existant du fichier. Si tu veux ajouter des lignes à un fichier existant à la place, utilise le mode"a"(append, ajout). - Supposer un ordre de colonnes fixe avec
csv.reader.row[0]n'est « le nom » que si la colonne nom est réellement en premier et le reste — unDictReaderévite toute cette catégorie de bug. - Ne pas convertir les colonnes numériques avant de faire des calculs dessus.
row["quiz1"] + row["quiz2"]avecDictReaderconcatène deux chaînes ("88" + "92"→"8892"), ça n'additionne pas deux nombres — un écho direct du piège deinput()à la semaine 1.
🧩 Défis
Étant donné un CSV avec les colonnes name,score, écris un programme qui calcule et affiche la moyenne des notes sur tous les étudiants du fichier.
Étends le programme précédent pour qu'il affiche aussi le nom de l'étudiant qui a la note la plus élevée.
Écris les moyennes par étudiant que tu as calculées ci-dessus dans un nouveau fichier summary.csv avec les colonnes name,average.
Que se passerait-il si une ligne du CSV avait une note manquante (une chaîne vide au lieu d'un nombre) ? Modifie ton programme pour qu'il ne plante pas sur cette ligne.
Refais le défi 3 en utilisant csv.DictWriter au lieu de csv.writer. Quelle version trouves-tu plus facile à lire, et pourquoi ?
Si tu voulais ajouter la ligne d'un étudiant supplémentaire à summary.csv sans effacer ce qui s'y trouve déjà, quel mode de fichier utiliserais-tu à la place de "w" ? Essaie-le.
🤔 Questions socratiques
- Pourquoi tout ce qui est lu depuis un CSV arrive-t-il comme un
str, même les colonnes qui semblent numériques ? Où ailleurs dans ce cours as-tu déjà vu ce même schéma « texte en entrée, conversion nécessaire » ? sorted(averages, key=lambda n: averages[n], reverse=True)— que changerait le retrait dereverse=True? Que changerait le retrait dekey=...entièrement en écrivant simplementsorted(averages)?- Tu as maintenant construit un petit pipeline : lire → transformer → résumer → écrire. De quels concepts des semaines 1 à 4 chaque étape dépendait-elle réellement ? Sans le contenu de quelle semaine ce projet aurait-il été impossible ?
with open(...) as f:ferme automatiquement le fichier même si une erreur survient dans le bloc. Peux-tu imaginer une raison pour laquelle un programme qui ouvre de nombreux fichiers au cours de sa vie, sans jamais les fermer correctement, pourrait finir par échouer de manière étrange ?csv.DictWritera besoin defieldnamesspécifié à l'avance, avant d'écrire quoi que ce soit. Pourquoi penses-tu qu'il l'exige, plutôt que de simplement déduire les colonnes à partir du premier dictionnaire que tu lui passes viawriterow?
✅ Quiz de la semaine
✅ Quiz de la semaine
🎁 Bonus : un premier aperçu des classes
Disponible une fois le quiz de la semaine réussi.