Aller au contenu principal

Programmation structurée (Python) · L1 · Section 5/11

I/O Fichiers

Progression

Points d’expérience : XPSérie de jours consécutifs : · —Progression du module : — / —compris

#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:

pythonpython

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 ligne

Modes 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:

pythonpython

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):

pythonpython

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 .txt

L'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):

pythonpython

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=False conserve 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:

pythonpython

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

Chargement de l’éditeur...

#Exercices

  1. 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.
  2. Journal horodaté. Écrivez une fonction journal(message) qui ajoute dans log.txt une ligne au format [2026-09-01T12:00:00] message, en mode ajout. Appelez-la deux fois et affichez le fichier.
  3. 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.
  4. 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
pythonpython

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é
pythonpython

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
pythonpython

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)))   # True

L'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
pythonpython

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 0

ligne.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 à chaque open.
  • É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() sans with: le fichier reste ouvert jusqu'au ramasse-miettes; en boucle, cela épuise les descripteurs. with referme 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.

#Quiz

Quel mode ajoute du contenu sans écraser un fichier existant ?
Quel mode ajoute du contenu sans écraser un fichier existant ?
Quelle option garantit un encodage cohérent multi-plateformes ?
Quelle option garantit un encodage cohérent multi-plateformes ?
Pourquoi itérer directement sur le fichier ouvert ?
Pourquoi itérer directement sur le fichier ouvert ?