Fichiers
& I/O
Lire, écrire et naviguer dans le système de fichiers — texte, CSV, JSON et binaire.
open() & modes d'ouverture
open(fichier, mode, encoding) est la fonction centrale. Elle retourne un objet fichier à utiliser dans un bloc with.
# Toujours spécifier l'encodage explicitement
f = open("data.txt", "r", encoding="utf-8")
# Autres encodages courants
open("old.txt", encoding="latin-1") # fichiers Windows anciens
open("data.csv", encoding="utf-8-sig") # CSV Excel (BOM)
# newline="" obligatoire pour csv.writer sur Windows
open("out.csv", "w", newline="", encoding="utf-8")
Ne jamais omettre encoding="utf-8" — l'encodage par défaut dépend du système (CP1252 sur Windows, UTF-8 sur Linux/Mac). Cela provoque des bugs silencieux difficiles à reproduire.
Lecture de fichiers
with open("data.txt", encoding="utf-8") as f:
# Tout le contenu en une chaîne
contenu = f.read()
# Remettre le curseur au début
f.seek(0)
# Une ligne à la fois
ligne = f.readline() # inclut le \n
# Toutes les lignes dans une liste
lignes = f.readlines() # [ligne1\n, ligne2\n, ...]
# ✓ Idiome préféré — itération ligne par ligne
# (ne charge pas tout en mémoire)
with open("grand_fichier.txt", encoding="utf-8") as f:
for ligne in f: # générateur — efficace
ligne = ligne.rstrip("\n")
print(ligne)
from pathlib import Path
def lire_fichier(chemin: str) -> str | None:
try:
with open(chemin, encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
print(f"Fichier introuvable : {chemin}")
except PermissionError:
print(f"Accès refusé : {chemin}")
except UnicodeDecodeError:
print(f"Encodage incorrect : {chemin}")
return None
# Raccourci pathlib (Python 3.5+)
contenu = Path("data.txt").read_text(encoding="utf-8")
Pour les fichiers volumineux, toujours itérer ligne par ligne plutôt que read() qui charge tout en mémoire. Un fichier de 2 Go avec read() = 2 Go de RAM.
Écriture de fichiers
# Écriture simple (écrase le fichier)
with open("sortie.txt", "w", encoding="utf-8") as f:
f.write("Ligne 1\n")
f.write("Ligne 2\n")
# writelines : liste de chaînes (sans \n auto)
lignes = ["alpha\n", "beta\n", "gamma\n"]
with open("sortie.txt", "w", encoding="utf-8") as f:
f.writelines(lignes)
# Ajout à la fin du fichier
with open("log.txt", "a", encoding="utf-8") as f:
f.write(f"[2024-03-01] Événement enregistré\n")
# Raccourci pathlib
from pathlib import Path
Path("sortie.txt").write_text(
"contenu", encoding="utf-8"
)
# print() accepte un paramètre file=
with open("rapport.txt", "w", encoding="utf-8") as f:
print("=== Rapport ===", file=f)
print(f"Total : {42}", file=f)
print("Fin", file=f, end="") # sans \n final
# Utile : flush() pour écrire immédiatement
# (éviter perte de données si crash)
with open("live.log", "a", encoding="utf-8") as f:
f.write("données critiques\n")
f.flush() # force l'écriture disque
Le mode "w" écrase le fichier sans avertissement. Pour écrire sans risque d'écraser, utiliser "x" qui lève FileExistsError si le fichier existe déjà.
Context manager — with
Le bloc with garantit la fermeture du fichier même en cas d'exception. C'est l'unique façon correcte d'ouvrir un fichier en Python.
# ✗ MAUVAIS — f.close() jamais appelé si exception
f = open("data.txt")
contenu = f.read() # ← si exception ici
f.close() # ← jamais exécuté !
# ✓ BON — fermeture garantie dans tous les cas
with open("data.txt", encoding="utf-8") as f:
contenu = f.read()
# f.close() appelé automatiquement ici
# Ouvrir plusieurs fichiers simultanément
with (
open("entree.txt", encoding="utf-8") as src,
open("sortie.txt", "w", encoding="utf-8") as dst,
):
for ligne in src:
dst.write(ligne.upper())
from contextlib import contextmanager
@contextmanager
def fichier_temporaire(chemin: str):
"""Crée un fichier, le supprime après usage."""
import os
f = open(chemin, "w", encoding="utf-8")
try:
yield f # ← corps du with
finally:
f.close()
os.remove(chemin) # toujours nettoyé
with fichier_temporaire("tmp.txt") as f:
f.write("données temporaires")
# fichier supprimé ici automatiquement
CSV
import csv
# Lecture
with open("employes.csv", encoding="utf-8") as f:
lecteur = csv.reader(f, delimiter=";")
en_tete = next(lecteur) # sauter la première ligne
for ligne in lecteur:
nom, age, salaire = ligne
print(f"{nom} — {age} ans")
# Écriture
donnees = [
["nom", "age", "ville"],
["Alice", 30, "Bruxelles"],
["Bob", 25, "Liège"],
]
with open("sortie.csv", "w",
newline="", encoding="utf-8") as f:
writer = csv.writer(f, delimiter=";")
writer.writerows(donnees)
import csv
# DictReader : chaque ligne = dict
with open("employes.csv", encoding="utf-8") as f:
for ligne in csv.DictReader(f, delimiter=";"):
print(ligne["nom"], ligne["salaire"])
# DictWriter : écrire depuis des dicts
employes = [
{"nom": "Alice", "age": 30, "ville": "BXL"},
{"nom": "Bob", "age": 25, "ville": "LGE"},
]
champs = ["nom", "age", "ville"]
with open("out.csv", "w", newline="",
encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=champs, delimiter=";")
w.writeheader()
w.writerows(employes)
Toujours ouvrir un CSV en écriture avec newline="" — sinon csv.writer ajoute des lignes vides supplémentaires sur Windows à cause du double \r\n.
JSON
import json
# Lire un fichier JSON → dict Python
with open("config.json", encoding="utf-8") as f:
config = json.load(f)
# Écrire un dict Python → fichier JSON
data = {
"nom": "Alice",
"scores": [10, 20, 30],
"actif": True,
}
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Sans fichier : chaîne ↔ dict
texte = json.dumps(data, indent=2) # dict → str
data2 = json.loads(texte) # str → dict
# Python → JSON
dict → object { }
list → array [ ]
tuple → array [ ]
str → string ""
int → number
float → number
True → true
False → false
None → null
# Attention : JSON ne supporte pas
# les objets Python arbitraires (datetime, set…)
import json
from datetime import datetime
def serialiser(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f"Non sérialisable : {type(obj)}")
json.dumps({"date": datetime.now()}, default=serialiser)
ensure_ascii=False permet d'écrire les caractères accentués tels quels (é, à, ü…) au lieu de les encoder en \u00e9. Toujours l'utiliser avec encoding="utf-8".
Fichiers binaires & pickle
# Copier un fichier binaire (image, PDF…)
with open("photo.jpg", "rb") as src, \
open("copie.jpg", "wb") as dst:
while chunk := src.read(4096): # walrus operator
dst.write(chunk)
# Lire les N premiers octets (magic bytes)
with open("fichier", "rb") as f:
magic = f.read(4)
if magic == b"\x89PNG":
print("C'est un PNG")
elif magic[:2] == b"\xff\xd8":
print("C'est un JPEG")
import pickle
# Sauvegarder n'importe quel objet Python
donnees = {"scores": [10, 20], "joueur": "Alice"}
with open("save.pkl", "wb") as f:
pickle.dump(donnees, f)
# Recharger exactement le même objet
with open("save.pkl", "rb") as f:
donnees = pickle.load(f)
Ne jamais charger un fichier pickle provenant d'une source non fiable — il peut exécuter du code arbitraire lors du load(). Pour l'échange de données, préférer JSON.
pathlib — chemins orientés objet
pathlib.Path remplace avantageusement os.path. Les chemins sont des objets avec des méthodes — plus lisibles et portables (Windows/Linux/Mac).
from pathlib import Path
p = Path("dossier/sous-dossier/fichier.txt")
# Propriétés du chemin
p.name # "fichier.txt"
p.stem # "fichier"
p.suffix # ".txt"
p.parent # Path("dossier/sous-dossier")
p.parts # ("dossier", "sous-dossier", "fichier.txt")
# Construction de chemins avec /
base = Path("projet")
config = base / "config" / "settings.json"
# Chemin absolu
p.resolve() # /home/user/projet/...
Path.cwd() # répertoire courant
Path.home() # répertoire home
# Tests d'existence et de type
p.exists() # True / False
p.is_file() # True si c'est un fichier
p.is_dir() # True si c'est un dossier
from pathlib import Path
dossier = Path("mon_projet/data")
# Créer un dossier (et parents si besoin)
dossier.mkdir(parents=True, exist_ok=True)
# Lister le contenu
for f in dossier.iterdir():
print(f.name)
# Chercher des fichiers (glob)
for f in Path(".").glob("**/*.py"): # récursif
print(f)
# Lire / écrire directement
texte = Path("readme.txt").read_text(encoding="utf-8")
Path("readme.txt").write_text("contenu", encoding="utf-8")
# Renommer et supprimer
p.rename(p.parent / "nouveau_nom.txt")
p.unlink() # supprimer un fichier
dossier.rmdir() # supprimer un dossier vide
# Métadonnées
stat = p.stat()
print(stat.st_size) # taille en octets
print(stat.st_mtime) # date de modification
os & shutil
import os
# Variables d'environnement
home = os.environ.get("HOME", "/tmp")
os.environ["MA_VAR"] = "valeur"
# Répertoire courant
cwd = os.getcwd()
os.chdir("/tmp")
# Exécuter une commande système
code = os.system("ls -la") # retourne le code de retour
# Chemin système (équivalent pathlib)
chemin = os.path.join("dossier", "fichier.txt")
os.path.exists(chemin)
os.path.basename(chemin) # "fichier.txt"
os.path.dirname(chemin) # "dossier"
import shutil
# Copier un fichier
shutil.copy("src.txt", "dst.txt") # sans métadonnées
shutil.copy2("src.txt", "dst.txt") # avec métadonnées
# Copier un dossier entier
shutil.copytree("src/", "dst/")
# Déplacer / renommer
shutil.move("ancien.txt", "nouveau.txt")
# Supprimer un dossier et tout son contenu
shutil.rmtree("dossier/") # DANGER : irréversible
# Créer une archive ZIP
shutil.make_archive("archive", "zip", "dossier/")
# Espace disque disponible
total, used, free = shutil.disk_usage("/")
print(f"Libre : {free // 2**30} Go")
Préférer pathlib pour la navigation et shutil pour les opérations "lourdes" (copie d'arbres, archives). Les deux coexistent très bien.
Pièges classiques
| Piège | Symptôme | Solution |
|---|---|---|
| Encodage omis | Caractères corrompus (é → é) selon le système | Toujours encoding="utf-8" |
| close() oublié | Données perdues, fichier verrouillé | Toujours with open(...) |
| newline= oublié (CSV) | Lignes vides sur Windows | open(..., newline="") |
| Mode "w" par erreur | Fichier écrasé silencieusement | Vérifier Path.exists() avant, ou utiliser "x" |
| Chemin relatif fragile | FileNotFoundError selon le répertoire d'exécution | Utiliser Path(__file__).parent / "fichier" |
| pickle non fiable | Erreur si classe modifiée entre sauvegarde et chargement | Versionner les classes ou utiliser JSON |
from pathlib import Path
# __file__ = chemin absolu du script courant
BASE = Path(__file__).parent # dossier du script
CONFIG = BASE / "config.json" # toujours trouvé
DATA = BASE / "data" / "input.csv"
# Fonctionne peu importe d'où on lance le script
Cheat sheet
Fichiers texte
| open(f, "r", encoding="utf-8") | Lecture |
| open(f, "w", encoding="utf-8") | Écriture (écrase) |
| open(f, "a", encoding="utf-8") | Ajout |
| f.read() | Tout le contenu |
| for ligne in f: | Ligne par ligne |
| f.write("texte\n") | Écrire |
CSV & JSON
| csv.DictReader(f) | Lire CSV → dicts |
| csv.DictWriter(f, fields) | Écrire CSV depuis dicts |
| json.load(f) | Fichier JSON → dict |
| json.dump(d, f, indent=2) | Dict → fichier JSON |
| json.loads(s) | String → dict |
| json.dumps(d) | Dict → string |
pathlib.Path
| Path("a") / "b" / "c" | Construire un chemin |
| p.exists() | Existe ? |
| p.mkdir(parents=True) | Créer dossier |
| p.glob("**/*.py") | Chercher des fichiers |
| p.read_text(encoding=) | Lire directement |
| p.unlink() | Supprimer |
shutil
| shutil.copy(src, dst) | Copier fichier |
| shutil.copytree(s, d) | Copier dossier |
| shutil.move(src, dst) | Déplacer |
| shutil.rmtree(path) | Supprimer dossier |
| shutil.make_archive() | Créer archive ZIP |