Travailler avec des fichiers texte : open, with, lecture et écriture
- Ouvrir un fichier dans le mode adapté — r, w ou a — et comprendre ce que fait chaque mode au contenu existant
- Utiliser
withpour que le fichier soit toujours fermé, même en cas d’erreur - Lire un fichier en entier, ligne par ligne, et y écrire et ajouter
- Construire un chemin vers un fichier relatif à l’emplacement du script avec pathlib, et gérer FileNotFoundError
Qu’allons-nous apprendre ?
- Ouvrir un fichier avec
openet les modes d’ouverture. - Pourquoi un fichier doit être fermé, et comment
withle fait automatiquement. - La lecture : le fichier entier, ligne par ligne.
- L’écriture et l’ajout.
- Un chemin robuste, qui ne dépend pas de l’endroit d’où l’on lance (promis dans le thème sur l’ordinateur, le système d’exploitation et le réseau !).
Pourquoi en avons-nous besoin ?
Le thème sur l’ordinateur, le système d’exploitation et le réseau : « tout ce qui est en RAM disparaît à la fin du programme ». Les fichiers sont le moyen de base pour enregistrer des informations sur le stockage : réglages, rapports, historique, données d’import et d’export. Le compteur qui revenait toujours à 1 dans memory_test.py ? Aujourd’hui, nous allons le faire survivre d’une exécution à l’autre.
Et dans le projet de POO, vous enregistrerez toutes les informations du système dans un fichier.
Explication à partir des bases
Ouvrir un fichier
open(path, mode) demande au système d’exploitation (un appel système !) l’accès au fichier, et renvoie un objet fichier à travers lequel on lit et on écrit.
Les modes d’ouverture
| Mode | Nom | Si le fichier existe | S’il n’existe pas | Actions |
|---|---|---|---|---|
"r" | Read (par défaut) | L’ouvre en lecture | FileNotFoundError | Lecture seule |
"w" | Write | Supprime tout le contenu ! | Le crée | Écriture |
"a" | Append | Garde le contenu, et écrit à la fin | Le crée | Écriture à la fin |
open("data.txt", "w") vide le fichier dès son ouverture, avant même que vous ayez écrit un seul caractère. Si vous vouliez ajouter, les informations précédentes sont déjà supprimées. Pour ajouter, utilisez toujours "a".
Pourquoi faut-il fermer un fichier ?
- L’écriture dans un fichier est d’abord accumulée dans un tampon (buffer) en mémoire, et réellement écrite sur le disque seulement à la fermeture du fichier ou quand le tampon est plein. Sans fermeture, une partie des informations risque de ne pas être enregistrée.
- Le système d’exploitation limite le nombre de fichiers qu’un processus peut garder ouverts. Les fichiers non fermés « fuient ».
- Sous Windows, un fichier ouvert est parfois verrouillé, et d’autres programmes ne peuvent pas y accéder.
with — la fermeture automatique
with open("notes.txt", "r") as file:
content = file.read()
# here the file is already closed - alwayswith garantit que le fichier est fermé dès que l’on quitte le bloc : à la fin normale, lors d’un return, et même quand une erreur est lancée. Vous vous souvenez de finally du thème exceptions et journalisation ? with fait exactement cela, en version courte. Ouvrez toujours les fichiers avec with.
open = prendre un livre sur l’étagère. close = le remettre. Sans with, il faut penser à le remettre, et si quelqu’un vous appelle en plein milieu (une erreur), le livre reste sur la table. with = un bibliothécaire qui remet le livre à sa place dès que vous vous levez de la table, quelle que soit la raison.
Les façons de lire
| Façon | Renvoie | Quand |
|---|---|---|
file.read() | Une seule chaîne avec tout le fichier | Petits fichiers |
file.readlines() | Une liste de lignes (chacune avec \n à la fin) | Quand on a besoin de toutes les lignes sous forme de liste |
for line in file: | Ligne par ligne | La façon recommandée. Fonctionne même pour des fichiers énormes, car elle ne charge pas tout en mémoire |
\n est le caractère de saut de ligne (newline). Chaque ligne du fichier se termine par lui. line.strip() le supprime (ainsi que les espaces aux extrémités).
L’écriture
file.write(text) écrit exactement le texte, sans passer automatiquement à la ligne (contrairement à print). Vous voulez une nouvelle ligne ? Ajoutez "\n" vous-même. Et write n’accepte qu’une chaîne : convertissez d’abord les nombres avec str().
Un chemin robuste avec pathlib
Dans le thème sur l’ordinateur, le système d’exploitation et le réseau, nous avons vu qu’un chemin relatif se mesure depuis le dossier d’où l’on a lancé. La solution professionnelle : construire le chemin par rapport à l’emplacement du fichier .py lui-même.
from pathlib import Path
BASE_DIR = Path(__file__).parent # the folder this .py file is in
DATA_FILE = BASE_DIR / "data.txt" # "/" joins path parts, on every OS__file__— une variable intégrée : le chemin du fichier actuel.Path(...).parent— le dossier qui le contient./entre unPathet une chaîne — joint correctement les parties du chemin sur tout système d’exploitation (\sous Windows,/sur Mac).
Maintenant, DATA_FILE pointe vers le même fichier quel que soit l’endroit d’où vous lancez.
Syntaxe
from pathlib import Path
path = Path(__file__).parent / "notes.txt"
# write (creates or overwrites)
with open(path, "w", encoding="utf-8") as file:
file.write("first line\n")
# append (adds to the end)
with open(path, "a", encoding="utf-8") as file:
file.write("another line\n")
# read everything
with open(path, "r", encoding="utf-8") as file:
content = file.read()
# read line by line
with open(path, "r", encoding="utf-8") as file:
for line in file:
print(line.strip())
path.exists() # True / Falseencoding="utf-8" — indiquez-le toujours. Sans lui, sous Windows l’encodage par défaut est différent, et un texte non anglais (comme l’hébreu ou les caractères accentués) ou des emoji seront écrits de façon illisible ou lanceront une UnicodeDecodeError.
Premier exemple de code
week-2/files/diary.py :
from pathlib import Path
DIARY_FILE = Path(__file__).parent / "diary.txt"
with open(DIARY_FILE, "w", encoding="utf-8") as file:
file.write("Day 1: started the course\n")
file.write("Day 2: learned the terminal\n")
with open(DIARY_FILE, "a", encoding="utf-8") as file:
file.write("Day 3: networks!\n")
with open(DIARY_FILE, "r", encoding="utf-8") as file:
content = file.read()
print(content)
print("File size in characters:", len(content))Sortie :
Day 1: started the course
Day 2: learned the terminal
Day 3: networks!
File size in characters: 71Et le fichier diary.txt a été créé dans le dossier. Ouvrez-le dans VS Code.
Analyse du code ligne par ligne
| Ligne | Explication |
|---|---|
Path(__file__).parent / "diary.txt" | Un chemin complet vers le fichier à côté du script. Fonctionne depuis n’importe quel dossier. |
open(DIARY_FILE, "w", ...) | Mode écriture : crée le fichier, ou le vide s’il existe. |
file.write("...\n") | Écrit du texte. Le \n crée un saut de ligne. |
La fin du premier bloc with | Le fichier est fermé et le contenu est écrit sur le disque. |
open(..., "a", ...) | Mode ajout : le contenu existant est conservé, et l’écriture se fait à la fin. |
file.read() | Tout le contenu sous forme d’une seule chaîne. |
| Une ligne vide dans la sortie | Le fichier se termine par \n, et print ajoute son propre \n. |
Essayez : relancez-le. Le fichier a-t-il grandi ? Non, car le mode "w" au début du script le vide à chaque exécution. Changez la première ouverture en "a" et lancez-le deux fois. Que se passe-t-il ?
Un autre exemple
Un compteur d’exécutions qui survit vraiment (le problème du thème sur l’ordinateur, le système d’exploitation et le réseau !) + le traitement d’un fichier de données ligne par ligne.
week-2/files/run_counter.py :
from pathlib import Path
COUNTER_FILE = Path(__file__).parent / "counter.txt"
def read_count():
if not COUNTER_FILE.exists():
return 0
with open(COUNTER_FILE, "r", encoding="utf-8") as file:
return int(file.read().strip())
def save_count(count):
with open(COUNTER_FILE, "w", encoding="utf-8") as file:
file.write(str(count))
def main():
count = read_count() + 1
save_count(count)
print("This program has run", count, "times")
if __name__ == "__main__":
main()Trois exécutions :
This program has run 1 times
This program has run 2 times
This program has run 3 timesMaintenant ça fonctionne, car le nombre est enregistré sur le stockage entre les exécutions, et pas seulement en RAM.
Le traitement d’un fichier de notes. Créez week-2/files/grades.txt :
Dana,90
Avi,72
Noa,abc
Tom,85
Rina,64week-2/files/grades_report.py :
from pathlib import Path
GRADES_FILE = Path(__file__).parent / "grades.txt"
REPORT_FILE = Path(__file__).parent / "report.txt"
def load_grades(path):
grades = {}
with open(path, "r", encoding="utf-8") as file:
for line_number, line in enumerate(file, start=1):
line = line.strip()
if line == "":
continue
name, grade_text = line.split(",")
try:
grades[name] = int(grade_text)
except ValueError:
print("Skipping line", line_number, "- bad grade:", repr(grade_text))
return grades
def write_report(grades, path):
with open(path, "w", encoding="utf-8") as file:
for name, grade in grades.items():
status = "PASS" if grade >= 65 else "FAIL"
file.write(name + ": " + str(grade) + " " + status + "\n")
average = sum(grades.values()) / len(grades)
file.write("Average: " + str(round(average, 1)) + "\n")
def main():
grades = load_grades(GRADES_FILE)
write_report(grades, REPORT_FILE)
print("Report written to", REPORT_FILE.name)
if __name__ == "__main__":
main()Sortie à l’écran :
Skipping line 3 - bad grade: 'abc'
Report written to report.txtreport.txt :
Dana: 90 PASS
Avi: 72 PASS
Tom: 85 PASS
Rina: 64 FAIL
Average: 77.8Analyse :
for line_number, line in enumerate(file, start=1)— parcourt le fichier ligne par ligne, avec un numéro de ligne, pour signaler où se trouve le problème.line.strip()— supprime le\n. Sans lui,int("90\n")fonctionne en fait, mais le nom arriverait avec des espaces en trop.if line == "": continue— sauter les lignes vides.name, grade_text = line.split(",")— découpage selon la virgule, et Unpacking en deux variables."PASS" if grade >= 65 else "FAIL"— une expression conditionnelle (Conditional Expression) : unif/elsecourt sur une ligne, qui renvoie l’une de deux valeurs.REPORT_FILE.name— seulement le nom du fichier, sans le chemin complet.Pathn’est pas qu’« un chemin ». Il a des méthodes utiles comme.exists()et.name.
Erreur courante
Erreur 1 : "w" au lieu de "a" — et des données supprimées. Nous l’avons vu plus haut. Avant chaque open(..., "w"), demandez-vous : « est-ce que la suppression du contenu précédent me convient ? »
Erreur 2 : FileNotFoundError à cause d’un chemin relatif.
with open("grades.txt") as file:Ne fonctionne que quand on lance depuis le même dossier. Depuis le bouton Run de VS Code, ou depuis un autre dossier :
FileNotFoundError: [Errno 2] No such file or directory: 'grades.txt'La correction : Path(__file__).parent / "grades.txt". Et pour les fichiers qui peuvent ne pas exister, vérifiez .exists() ou attrapez FileNotFoundError.
Erreur 3 : écrire un nombre avec write.
file.write(42)TypeError: write() argument must be str, not intLa correction : file.write(str(42)).
Erreur 4 : oublier \n.
file.write("line one")
file.write("line two")Dans le fichier : line oneline two, sur une seule ligne. write n’ajoute pas de saut de ligne.
Erreur 5 : lire deux fois depuis le même fichier ouvert.
with open(path, encoding="utf-8") as file:
first = file.read()
second = file.read()
print(repr(second)) # ''Le fichier « se souvient » de l’endroit où il lit (une position de lecture). Après read(), la position est à la fin, donc la seconde lecture renvoie une chaîne vide.
Exercices
Les exercices n’utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d’abord seul, et n’ouvrez la solution qu’ensuite.
Exercice 1: Écrire et lire
Écrivez deux lignes dans le fichier diary.txt : Day 1: started et Day 2: networks. Puis lisez le fichier et affichez son contenu.
Résultat attendu :
Day 1: started
Day 2: networksAfficher la solution
with open("diary.txt", "w", encoding="utf-8") as file:
file.write("Day 1: started\n")
file.write("Day 2: networks\n")
with open("diary.txt", "r", encoding="utf-8") as file:
print(file.read(), end="")Day 1: started
Day 2: networkswithferme le fichier automatiquement, même en cas d’erreur.writen’ajoute pas de saut de ligne tout seul — il faut\n.end=""— parce que le contenu se termine déjà par\n.
Exercice 2: w face à a
with open("log.txt", "w", encoding="utf-8") as file:
file.write("first\n")
with open("log.txt", "w", encoding="utf-8") as file:
file.write("second\n")
with open("log.txt", "a", encoding="utf-8") as file:
file.write("third\n")Que contiendra le fichier à la fin ? Expliquez.
Afficher la solution
with open("log.txt", "w", encoding="utf-8") as file:
file.write("first\n")
with open("log.txt", "w", encoding="utf-8") as file:
file.write("second\n")
with open("log.txt", "a", encoding="utf-8") as file:
file.write("third\n")
with open("log.txt", "r", encoding="utf-8") as file:
print(file.read(), end="")second
third"w"supprime le fichier et repart de zéro — c’est pourquoifirsta été supprimé quand le fichier a été ouvert en"w"la deuxième fois."a"ajoute à la fin, doncthirda été ajouté aprèssecond.
Exercice 3: Ligne après ligne
Dans le fichier grades.txt, chaque ligne est name,grade. Écrivez un code qui lit le fichier ligne après ligne, et affiche chaque nom avec sa note. Sautez les lignes vides.
Créez le fichier ainsi :
with open("grades.txt", "w", encoding="utf-8") as file:
file.write("dana,90\n\navi,75\nnoa,88\n")Résultat attendu :
dana 90
avi 75
noa 88Afficher la solution
with open("grades.txt", "w", encoding="utf-8") as file:
file.write("dana,90\n\navi,75\nnoa,88\n")
with open("grades.txt", "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if not line:
continue
name, grade_text = line.split(",")
print(name, int(grade_text))dana 90
avi 75
noa 88for line in file— parcourt le fichier ligne après ligne.strip()— supprime le\net les espaces aux extrémités.split(",")— découpe la ligne, et l’Unpacking la place dans deux variables.
Exercice 4: Une entrée défectueuse
Le même fichier de notes, mais il contient maintenant une ligne défectueuse. Lisez le fichier : une ligne dont la note n’est pas un nombre — sautez-la et affichez un message avec le numéro de ligne. À la fin, affichez la moyenne des notes valides.
with open("grades.txt", "w", encoding="utf-8") as file:
file.write("dana,90\navi,abc\nnoa,80\n")Résultat attendu :
line 2 skipped: avi,abc
Average: 85.0Afficher la solution
with open("grades.txt", "w", encoding="utf-8") as file:
file.write("dana,90\navi,abc\nnoa,80\n")
grades = []
with open("grades.txt", "r", encoding="utf-8") as file:
for number, line in enumerate(file, start=1):
line = line.strip()
name, grade_text = line.split(",")
try:
grades.append(int(grade_text))
except ValueError:
print("line", number, "skipped:", line)
print("Average:", sum(grades) / len(grades))line 2 skipped: avi,abc
Average: 85.0enumerate(file, start=1) donne aussi le numéro de ligne. Le try/except des leçons précédentes permet de sauter une ligne défectueuse sans planter.
Exercice 5: Un fichier qui n’existe pas
- Que se passera-t-il si l’on essaie d’ouvrir en lecture un fichier qui n’existe pas ?
- Écrivez une fonction
load_tasks(path)qui renvoie les lignes du fichier sous forme de liste, et une liste vide si le fichier n’existe pas. UtilisezPath(path).exists().
print(load_tasks("todo.txt"))
with open("todo.txt", "w", encoding="utf-8") as file:
file.write("buy milk\ncall mom\n")
print(load_tasks("todo.txt"))Résultat attendu :
[]
['buy milk', 'call mom']Afficher la solution
- Une
FileNotFoundErrorsera lancée.
from pathlib import Path
def load_tasks(path):
if not Path(path).exists():
return []
tasks = []
with open(path, "r", encoding="utf-8") as file:
for line in file:
tasks.append(line.strip())
return tasks
print(load_tasks("todo.txt"))
with open("todo.txt", "w", encoding="utf-8") as file:
file.write("buy milk\ncall mom\n")
print(load_tasks("todo.txt"))[]
['buy milk', 'call mom']Vérifiez avant l’ouverture si le fichier existe. À la première exécution d’un programme, il n’y a pas encore de fichier — et c’est un état valide, pas une erreur.
Résumé
open(path, mode, encoding="utf-8"):"r"lecture (par défaut),"w"écriture qui supprime,"a"ajout à la fin.- Toujours
with: le fichier est fermé automatiquement, même en cas d’erreur. - Lecture :
read()(tout),for line in file(ligne par ligne, recommandé).strip()supprime le\n. writen’écrit qu’unstr, et sans saut de ligne. Ajoutez"\n"vous-même.- Un chemin robuste :
Path(__file__).parent / "file.txt". Vérifiez l’existence avec.exists()ou attrapezFileNotFoundError.