Les fichiers

On va montrer comment on peut utiliser python pour interagir avec des fichiers.

Fonction principale

La principale fonction qui va nous servir dans cette partie est open. Les deux arguments qui vont nous intéresser sont

REMARQUE notez que pour les deux modes d’écritures, on crée le fichier si jamais il n’existe pas.

Si je me place dans un répertoire contenante un seul fichier essai dont le contenu est

Première ligne.
Deuxième ligne.
Et une troisième.

alors on peut avoir la session:

>>> fichier = open("essai", "r")
>>> type(fichier)
<class '_io.TextIOWrapper'>
>>> dir(fichier)
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'reconfigure', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'write_through', 'writelines']
>>> fichier.close()

ATTENTION notez bien que lorsqu’on ouvre un fichier de cette façon il faudra bien penser à le fermer avec la méthode close lorsqu’on en aura plus besoin. On verra pourquoi plus loin ainsi qu’une syntaxe alternative.

REMARQUE le type de l’objet renvoyé par open est relativement complexe, notons juste que le IO signifie InputOutput. Cet objet possède de nombreuses méthodes mais on se concentrera sur read et readlines pour la lecture, write pour l’écriture. Notez qu’elles sont toutes présentes même si le fichier est ouvert officiellement en mode lecture!

Lecture

Un fichier ouvert en lecture est un itérable et on récupère alors les lignes.

>>> for ligne in fichier:
...     print(ligne)
...
Première ligne.

Deuxième ligne.

Et une troisième.

Notez que l’on a le saut de ligne de la chaine et celui de print qui se juxtaposent. Pour enlever les caractères “blancs” en début et en fin de str on peut utiliser la méthode strip.

>>> fichier = open("essai", "r")
>>> lignes = [ligne.strip() for ligne in fichier]
>>> fichier.close()
>>> lignes
['Première ligne.', 'Deuxième ligne.', 'Et une troisième.']

On peut aussi utiliser readlines pour récupérer la liste des lignes (ces dernières étant des str, et gardant leur caractère de saut de ligne \n)

>>> fichier = open("essai", "r")
>>> fichier.readlines()
['Première ligne.\n', 'Deuxième ligne.\n', 'Et une troisième.\n']
>>> fichier.readlines()
[]
>>> fichier.close()

ATTENTION notez bien qu’après la première lecture c’est comme si le curseur imaginaire était en bout de fichier. Un nouvel appel à la méthode readlines renvoie donc une liste vide!

Finalement mentionnons que la méthode read permet de tout récupérer en une chaine de caractères.

>>> fichier = open("essai", "r")
>>> texte = fichier.read()
>>> fichier.close()
>>> texte
'Première ligne.\nDeuxième ligne.\nEt une troisième.\n'

ATTENTION lorsque les fichiers sont trop gros on préfèrera utiliser la toute première version pour gérer les lignes les unes après les autres.

Il y a des possibilités de lecture plus fines, basées sur la manipulation du curseur imaginaire mais les précédentes suffisent dans la majorité des cas. Vous pouvez regarder help(fichier.seek) pour plus d’informations.

Écriture

Pour écrire on utilisera la méthode write à laquelle on passe une chaine de caractère.

>>> fichier = open("nouveau", "w")
>>> fichier.write("première ligne")
14
>>> fichier.write("deuxième ligne")
14
>>> fichier.close()
>>> fichier_bis = open("nouveau", "r")
>>> print(fichier_bis.read())
première lignedeuxième ligne
>>> fichier_bis.close()

ATTENTION on n’oubliera pas d’insérer des sauts de ligne si jamais on en veut.

ATTENTION notez bien la différence entre w:

>>> ecriture = open("nouveau", "w")
>>> ecriture.write("Premier passage\n")
16
>>> ecriture.close()
>>> ajout = open("nouveau", "w")
>>> ajout.write("Deuxième passage.\n")
18
>>> ajout.close()
>>> lecture = open("nouveau", "r")
>>> print(lecture.read())
Deuxième passage.

>>> lecture.close()

et a

>>> ecriture = open("nouveau", "w")
>>> ecriture.write("Premier passage\n")
16
>>> ecriture.close()
>>> ajout = open("nouveau", "a")
>>> ajout.write("Deuxième passage.\n")
18
>>> ajout.close()
>>> lecture = open("nouveau", "r")
>>> print(lecture.read())
Premier passage
Deuxième passage.

>>> lecture.close()

REMARQUE la méthode write renvoie le nombre de caractères écrits.

Pourquoi fermer

L’objet renvoyé par open est en fait un buffer: une zone mémoire intermédiaire entre le disque et python. Ceci afin d’optimiser les opérations d’écriture sur disque qui prennent beaucoup plus de temps que l’accès en mémoire vive.

En particulier toutes les écritures dans le buffer ne sont pas instantanément répercutées sur le disque. Il peut y avoir attente

Or si python plante entre temps ou bien qu’on effectue des écritures/lectures en parallèles, le buffer peut ne pas être recopié sur le disque à temps.

>>> ecriture = open("test_buffer", "w")
>>> ecriture.write("Bien écrit")
10
>>> lecture = open("test_buffer", "r")
>>> print(lecture.read())

>>> lecture.close()
>>> ecriture.close()
>>> lecture = open("test_buffer", "r")
>>> print(lecture.read())
Bien écrit
>>> lecture.close()

Python propose une syntaxe pour assurer que le fichier soit fermé quoi qu’il arrive. Il s’agit de l’instruction composée with qui crée un gestionnaire de contexte (objet très général mais que l’on ne verra que dans ce cas particulier).

>>> with open("nouveau", "r") as fichier:
...     print(fichier.read())
...
Premier passage
Deuxième passage.

Le point clef est qu’à la fin du corps principal de l’instruction with, le fichier sera fermé, même si une erreur a été soulevée.

Pour naviguer plus facilement dans l’arborescence de fichier et faciliter le portage du code entre différents systèmes (Linux, OSX, Windows). On va montrer comment utiliser la bibliothèque pathlib.

On se place dans un répertoire avec l’arborescence suivante:

 .
├──  autre
│  └──  alt.txt
├──  essai
├──  nouveau
├──  rep
│  ├──  autre.txt
│  ├──  encore.txt
│  ├──  fichier1
│  ├──  fichier2
│  └──  fichier3
└──  test_buffer

On peut alors naviguer de la façon suivante.

>>> from pathlib import Path
>>> racine = Path(".")
>>> print(racine)
.
>>> racine = racine.resolve()
>>> print(racine)
/home/vincent/Code/python/demo_repertoire/foad
>>> type(racine)
<class 'pathlib.PosixPath'>

On peut maintenant crée de nouveau objets Path représentant des répertoires ou des fichiers (ce qu’on peut détecter) de la façon suivante.

>>> from pathlib import Path
>>> racine = Path(".").resolve()
>>> sous_rep = racine / "rep"
>>> sous_rep.is_dir()
True
>>> sous_rep.is_file()
False
>>> sous_rep
PosixPath('/home/vincent/Code/python/demo_repertoire/foad/rep')
>>> chemin_fichier = racine / "essai"
>>> chemin_fichier.is_file()
True
>>> chemin_fichier.is_dir()
False
>>> chemin_fichier
PosixPath('/home/vincent/Code/python/demo_repertoire/foad/essai')

ATTENTION windows utilise des \ plutôt que des / dans l’arborescence mais on utilisera toujours des / entre objets Path et str.

ATTENTION rien n’empêche de créer un chemin qui n’existe pas mais on peut tester cette propriété grâce à la méthode exists.

>>> from pathlib import Path
>>> racine = Path(".")
>>> fantaisiste = racine / "blabla"
>>> fantaisiste.exists()
False
>>> racine.exists()
True

On peut faire des recherches de chemins avec la méthode glob. Celle-ci accepte des * pour permettre la recherche de motifs et ** pour des répertoires.

>>> from pathlib import Path
>>> racine = Path(".")
>>> list(racine.glob("*"))
[PosixPath('autre'), PosixPath('test_buffer'), PosixPath('nouveau'), PosixPath('rep'), PosixPath('essai')]
>>> list(racine.glob("rep/*"))
[PosixPath('rep/encore.txt'), PosixPath('rep/autre.txt'), PosixPath('rep/fichier3'), PosixPath('rep/fichier2'), PosixPath('rep/fichier1')]
>>> list(racine.glob("rep/fichier*"))
[PosixPath('rep/fichier3'), PosixPath('rep/fichier2'), PosixPath('rep/fichier1')]
>>> list(racine.glob("**/*.txt"))
[PosixPath('autre/alt.txt'), PosixPath('rep/encore.txt'), PosixPath('rep/autre.txt')]

Concluons cette très brève introduction à pathlib en mentionnant que l’on peut fournir un objet Path à open pour passer d’un chemin au fichier qu’il représente.

>>> from pathlib import Path
>>> racine = Path(".")
>>> list(racine.glob("*"))
[PosixPath('autre'), PosixPath('test_buffer'), PosixPath('nouveau'), PosixPath('rep'), PosixPath('essai')]
>>> chemin = racine / "essai"
>>> with open(chemin, "r") as fichier:
...     print(fichier.read())
...
Première ligne.
Deuxième ligne.
Et une troisième.

>>> for chemin in racine.glob("**/*.txt"):
...     with open(chemin, "a") as fichier:
...             fichier.write(f"ceci est {chemin}")
...
25
26
25
>>> for chemin in racine.glob("**/*.txt"):
...     with open(chemin, "r") as fichier:
...             print(fichier.read())
...
ceci est autre/alt.txt
ceci est rep/encore.txt
ceci est rep/autre.txt

Pour aller plus loin

Pour l’ouverture de fichiers:

>>> help(open)

Pour la navigation consulter la documentation.