Programmation structurée (Python) · L1 · Section 5/11
I/O Fichiers
Progression
#Fichiers (entrées/sorties)
Les fichiers persistent les données entre les exécutions et échangent des informations entre programmes. Trois règles de base en Python: ouvrir avec with, préciser l'encodage, traiter les erreurs d'accès.
Prérequis: boucles, chaînes de caractères, exceptions (pour la gestion d'erreurs).
Objectifs d'apprentissage:
- Lire et écrire des fichiers texte avec le bon mode et le bon encodage.
- Traiter un fichier ligne à ligne sans tout charger en mémoire.
- Sérialiser des données en JSON et échanger avec
json.dumps/json.loads. - Manipuler des chemins portables avec
pathlib.
Dans les playgrounds intégrés (Pyodide), le système de fichiers est virtuel et vit en mémoire pour la durée de la session: les principes restent identiques, seul le stockage change.
#Ouverture avec with
Le context manager with garantit la fermeture du fichier, même en cas d'exception:
1with open('demo.txt', 'w', encoding='utf-8') as f:2 f.write('première ligne\n')3 f.write('seconde ligne\n')4 5with open('demo.txt', encoding='utf-8') as f:6 print(f.read())7# Sortie:8# première ligne9# seconde ligneModes courants: 'r' lecture (défaut), 'w' écriture avec écrasement, 'a' ajout en fin, 'r+' lecture et écriture. 'w' détruit le contenu existant sans confirmation: pour ajouter, utilisez 'a'.
Toujours préciser encoding='utf-8': sans lui, l'encodage dépend du système et un fichier écrit sur une plateforme peut se lire incorrectement sur une autre.
#Lecture ligne à ligne
Pour un gros fichier, itérer sur le fichier traite une ligne à la fois sans tout charger en mémoire:
1try:2 with open('notes.csv', encoding='utf-8') as f:3 for ligne in f:4 champs = ligne.strip().split(',')5 print(champs)6except FileNotFoundError:7 print('Fichier absent: créez-le d\'abord')ligne.strip() retire le saut de ligne final et les espaces; split(',') découpe en liste de chaînes. Pour convertir en nombres, appliquez int ou float explicitement sur chaque champ.
#Chemins avec pathlib
pathlib.Path représente les chemins de façon portable (le séparateur change selon le système):
1from pathlib import Path2 3donnees = Path('donnees')4donnees.mkdir(exist_ok=True)5 6fichier = donnees / 'exemple.txt'7fichier.write_text('café', encoding='utf-8') # écrit et referme8print(fichier.read_text(encoding='utf-8')) # café9print(fichier.exists(), fichier.suffix) # True .txtL'opérateur / assemble les chemins; mkdir(exist_ok=True) évite l'erreur si le dossier existe déjà.
#JSON: structurer les données
Le format JSON représente des dictionnaires, listes, chaînes, nombres, booléens et null. dumps sérialise vers une chaîne, loads désérialise depuis une chaîne (s pour string):
1import json2 3personne = {"nom": "Ada", "age": 36, "langages": ["python", "c"]}4 5with open('personne.json', 'w', encoding='utf-8') as f:6 json.dump(personne, f, ensure_ascii=False, indent=2)7 8with open('personne.json', encoding='utf-8') as f:9 copie = json.load(f)10 11print(copie == personne) # True12print(json.loads('{"a": [1, 2]}')['a']) # [1, 2]
ensure_ascii=Falseconserve les accents lisibles dans le fichier.json.load(f)lit depuis un fichier ouvert,json.loads(s)depuis une chaîne.
Un JSON invalide lève json.JSONDecodeError: interceptez-le quand le contenu vient de l'extérieur.
#CSV avec le module dédié
Pour des CSV réalistes (champs avec guillemets, séparateurs), préférez le module csv au split manuel:
1import csv2 3with open('notes.csv', 'w', newline='', encoding='utf-8') as f:4 ecrivain = csv.writer(f)5 ecrivain.writerow(['nom', 'note'])6 ecrivain.writerow(['Ada', 19])7 8with open('notes.csv', newline='', encoding='utf-8') as f:9 for rangée in csv.reader(f):10 print(rangée)11# Sortie:12# ['nom', 'note']13# ['Ada', '19']#Playground
#Exercices
- Moyenne d'une colonne. Écrivez un fichier CSV à trois colonnes
nom,matiere,note(au moins 4 lignes), puis calculez la moyenne des notes. Vérifiez à la main sur votre jeu de données. - Journal horodaté. Écrivez une fonction
journal(message)qui ajoute danslog.txtune ligne au format[2026-09-01T12:00:00] message, en mode ajout. Appelez-la deux fois et affichez le fichier. - Aller-retour JSON. Écrivez une liste de dictionnaires (nom, âge) dans
gens.json, relisez-la, et vérifiez que chaque élément relu est égal à l'original. - Compteur de lignes robuste. Lisez un fichier ligne à ligne et affichez le nombre de lignes non vides, en gérant l'absence du fichier par un message plutôt qu'un plantage.
#Corrections
Correction: moyenne d'une colonne
1import csv2 3with open('notes.csv', 'w', newline='', encoding='utf-8') as f:4 csv.writer(f).writerows([5 ['nom', 'matiere', 'note'],6 ['Ada', 'math', 18],7 ['Alan', 'math', 12],8 ['Grace', 'math', 16],9 ])10 11with open('notes.csv', newline='', encoding='utf-8') as f:12 lecteur = csv.DictReader(f)13 notes = [float(r['note']) for r in lecteur]14 DictReader transforme chaque rangée en dictionnaire utilisant la première ligne comme clés: l'accès par nom de colonne évite les erreurs d'index. Vérification manuelle: (18 + 12 + 16) / 3.
Correction: journal horodaté
1from datetime import datetime2 3def journal(message: str, chemin: str = 'log.txt') -> None:4 horodatage = datetime.now().isoformat(timespec='seconds')5 with open(chemin, 'a', encoding='utf-8') as f:6 f.write(f"[{horodatage}] {message}\n")7 8journal('démarrage')9journal('traitement terminé')10 11with open('log.txt', encoding='utf-8') as f:12 print(f.read())Le mode 'a' ajoute en fin sans écraser: chaque appel conserve l'historique. C'est le principe d'un fichier journal.
Correction: aller-retour JSON
1import json2 3gens = [4 {"nom": "Ada", "age": 36},5 {"nom": "Alan", "age": 41},6]7 8with open('gens.json', 'w', encoding='utf-8') as f:9 json.dump(gens, f, ensure_ascii=False, indent=2)10 11with open('gens.json', encoding='utf-8') as f:12 relus = json.load(f)13 14print(all(a == b for a, b in zip(gens, relus))) # TrueL'aller-retour réussi valide la sérialisation: les types (int reste int) et le contenu sont préservés.
Correction: compteur de lignes robuste
1from pathlib import Path2 3def compter_lignes(chemin: str) -> int:4 if not Path(chemin).exists():5 print(f"Fichier introuvable: {chemin}")6 return 07 with open(chemin, encoding='utf-8') as f:8 return sum(1 for ligne in f if ligne.strip())9 10with open('texte.txt', 'w', encoding='utf-8') as f:11 f.write("ligne 1\n\n \nligne 2\n")12 13print(compter_lignes('texte.txt')) # 214print(compter_lignes('absent.txt')) # Fichier introuvable: absent.txt puis 0ligne.strip() distingue la ligne vide ou blanche de la ligne utile. Le générateur passé à sum évite de construire une liste en mémoire.
#Pièges fréquents
- Encodage implicite: sans
encoding='utf-8', un fichier écrit sur un système peut afficher des caractères corrompus sur un autre. Précisez-le à chaqueopen. - Écrasement par
'w': ouvrir en écriture un fichier existant le vide immédiatement. Pour préserver, copiez d'abord ou ouvrez en'a'. open(...).read()sanswith: le fichier reste ouvert jusqu'au ramasse-miettes; en boucle, cela épuise les descripteurs.withreferme toujours.- Confondre texte et bytes: les fichiers texte décodent des octets; un fichier binaire (image, PDF) s'ouvre avec
'rb'/'wb'et se traite sans encodage.