.. _tut-files:
************
Les fichiers
************
Une vidéo de présentation des fichiers...
.. raw:: html
Python peut traiter des fichiers texte et des fichiers binaires (images, sons, etc...). Pour ce cours, nous travaillerons exclusivement avec des fichiers contenant du texte. Nous utiliserons à titre d'exemple l'oeuvre de Corneille, Le Cid, contenue dans le fichier :download:`lecid.txt <../data/lecid.txt>`.
Ouverture du fichier
====================
Ouvrir un fichier c'est établir la connexion avec la ressource contenant les données.
On ouvre un fichier avec la fonction :func:`open` qui peut ne nécessiter qu'un seul argument, le nom du fichier à ouvrir. Si aucune arborescence n'est précisée, le répertoire dans lequel le fichier est recherché est celui depuis lequel a été lancé l'interpréteur Python::
>>> f = open('lecid.txt')
>>> f
<_io.TextIOWrapper name='index.rst' mode='r' encoding='cp1252'>
Le mode d'ouverture par défaut du fichier est en lecture seule : ``mode='r'`` et l'encodage est celui du terminal depuis lequel a été lancé l'interpréteur Python : ``encoding='cp1252'``. Ce sera cependant une bonne pratique que de préciser explicitement ces deux paramètres.
:func:`open` retourne un `file-object `_ de type :class:`io.TextIOWrapper`. Cette classe hérite de :class:`io.TextIOBase` et :class:`io.IOBase`.
>>> type(f)
>>> import io
>>> isinstance(f, io.TextIOBase)
True
>>> isinstance(f, io.IOBase)
True
L'objet retourné par :func:`open` dispose de plusieurs méthodes. Ici seules les plus importantes ont été conservées pour l'affichage::
>>> dir(f)
[..., 'close', ..., 'read', ..., 'readline', 'readlines', ..., 'write', 'writelines']
L'encodage utilisé est également disponible dans un attribut::
>>> f.encoding # interpréteur Python lancé depuis une console Windows
'cp1252'
Comme précisé plus haut, c'est cependant une bonne pratique de **préciser explicitement l'encodage du fichier** dans les paramètres de la fonction :func:`open`. C'est même indispensable lorsque le fichier comporte des **caractères accentués**.
>>> f = open('lecid.txt', mode='r', encoding='utf8')
>>> f.encoding
'utf8'
Le nom du fichier est accessible dans l'attribut :attr:`~io.FileIO.name`::
>>> f.name
'lecid.txt'
Fermeture du fichier
====================
Fermer un fichier c'est rompre la connexion avec la ressource contenant les données, et donc libérer cette dernière pour le système d'exploitation.
La fonction :meth:`~io.IOBase.close` permet de fermer le fichier une fois la lecture effectuée::
>>> f.closed
False
>>> f.close()
>>> f.closed
True
La bonne pratique de manipulation d'un fichier consiste donc en 3 étapes :
#. ouvrir le fichier
#. réaliser les opérations de lecture/écriture
#. fermer le fichier
La construction :keyword:`with`
===============================
L'action d'ouverture-fermeture d'un fichier est non seulement très courante mais la fermeture du fichier, après les opérations de lecture/écriture, nécessaire. La construction :keyword:`with` est une façon concise, efficace et élégante de fermer le fichier après les opérations de lecture/écriture::
>>> with open('lecid.txt', mode='r', encoding='utf8') as f:
... s = f.read()
...
>>> f.closed
True
Cette méthode est sure car le fichier est automatiquement fermé après l'exécution des instructions du bloc :keyword:`with`.
:keyword:`with` est un *context manager*. Il permet de définir des opérations à exécuter :
- avant les instructions du bloc ;
- après les instructions du bloc.
.. note::
:keyword:`with` est utilisé ici avec un objet de type :class:`io.TextIOWrapper` mais peut être employé avec tout objet implémentant les méthodes :
- :meth:`__enter__` ;
- et :meth:`__exit__`.
Lecture du contenu
==================
Il existe plusieurs façon de lire le contenu d'un fichier:
On peut récupérer l'intégralité du contenu dans une chaîne de caractères avec la méthode :meth:`~io.TextIOBase.read` comme dans l'exemple ci dessus.
>>> s = f.read()
>>> type(s)
>>> len(s) # nombre de caractères
95302
>>> s[-46:]
'Laisse faire le temps, ta vaillance et ton roi'
Un autre appel à :meth:`~io.TextIOBase.read` retourne la chaîne vide (car tout le fichier a été lu par l'instruction précédente)::
>>> s = f.read()
>>> s
''
On peut aussi récupérer l'intégralité du contenu dans une liste de chaînes de caractères avec :meth:`~io.IOBase.readlines`
>>> with open('lecid.txt', mode='r', encoding='utf8') as f:
... l = f.readlines()
>>> type(l)
>>> len(l) # nombre de lignes
3236
>>> l[-1]
'Laisse faire le temps, ta vaillance et ton roi'
>>> for i in l[785:787]:
... print(i, end='')
...
Je suis jeune, il est vrai ; mais aux âmes bien nées
La valeur n'attend point le nombre des années.
On peut également lire le fichier ligne par ligne avec :meth:`~io.IOBase.readline`::
>>> f.readline()
'LE CID\n'
>>> f.readline()
'======\n'
>>> f.readline()
'\n'
:meth:`~io.IOBase.readline` n'a d'intérêt qu'encapsulé dans une boucle :keyword:`for` ou :keyword:`while` simple et que si la taille du fichier est trop importante pour un chargement total en mémoire. Sinon :meth:`~io.IOBase.readlines` sera préférée::
>>> with open('lecid.txt', mode='r', encoding='utf8') as f:
... for i in range(10):
... print(f.readline())
Attention, la construction ``for ... in`` effectue un appel à :meth:`~io.IOBase.readline`. On peut donc lire le fichier contenant les entiers consécutifs de 0 à 9 comme suit::
>>> with open('entiers.txt', mode='r', encoding='utf8') as f:
... for l in f:
... print(l)
0
1
2
3
4
5
6
7
8
9
Si on ajoute un autre appel à :meth:`~io.IOBase.readline` dans la boucle en plus de celui effectué par la construction ``for ... in f:``, la moitié des lignes ne sont plus affichées.
>>> with open('entiers.txt', mode='r', encoding='utf8') as f:
... for l in f:
... print(f.readline(), end='')
...
1
3
5
7
9
.. note::
Le parcours du fichier se fait ici en itérant sur un objet de type `file-object `_, ce qui en fait un **itérable** au même titre que les containers et les chaînes de caractères.
Ecriture dans un fichier
========================
On écrit dans un fichier avec la méthode :meth:`~io.TextIOBase.write`. Il faut auparavant ouvrir le fichier en écriture (``mode='w'``)::
>>> with open('file.txt', mode='w', encoding='utf8') as f:
... f.write("M. Churchill, si j'étais votre femme, je mettrais du poison dans votre café !\n")
... f.write("Lady Astor, si j'étais votre mari, je le boirais !")
...
78
50
La méthode :meth:`~io.TextIOBase.write` retourne le nombre de caractères écrits dans le fichier.
On peut également utiliser la méthode :meth:`~io.IOBase.writelines` qui permet d'écrire dans un fichier un ensemble de chaines de caractères stockées dans une liste.
.. _csv-files:
Les fichiers csv
================
Le format ``csv`` `Comma Separated Value `_ est couramment utilisé pour échanger des données au format texte. Le module :mod:`csv` de Python dispose de méthodes spécifiques pour lire ce genre de format.
On considère le fichier des observations météo de la station d'Edimbourg en Ecosse :download:`Edinburgh_2015_Oct.csv <../data/Edinburgh_2015_Oct.csv>`.
Ouvrir ce fichier avec un éditeur de texte pour en observer la structure.
Pour en lire le contenu, Python utilise la fonction :func:`~csv.reader` qui retourne un itérable::
>>> with open('Edinburgh_2015_Oct.csv', 'r') as f:
... r = csv.reader(f)
... l = list(r) # l'itérable est converti en liste
...
Le format `Comma Separated Value `_ ne fixe pas le délimiteur utilisé pour séparer les données. Pour le format anglo-saxon, la virgule ``,`` est utilisée.
>>> r.dialect.delimiter
','
La taille de la liste construite à partir de l'objet retourné par :func:`~csv.reader` est égale au nombre de lignes du fichier::
>>> len(l)
44703
La première ligne contient l'en tête::
>>> l[0]
['date-time', 'atmospheric pressure (mBar)', 'rainfall (mm)', 'wind speed (m/s)', 'wind direction (degrees)', 'surface temperature (C)', 'relative humidity (%)', 'solar flux (Kw/m2)', 'battery (V)']
A partir de la seconde, on trouve les données::
>>> l[1] # la première ligne de données
['2015/10/01 00:01', '1035', '0', '0', '0', '9.28', '93.9', '0', '13.77']
.. admonition:: A expérimenter...
On considère le fichier :download:`synop.2015110112.csv <../data/synop.2015110112.csv>` qui contient des données produites par `Météo France `_ au format ``csv``.
Ouvrir ce fichier avec un éditeur de texte pour en observer la structure.
Le séparateur décimal français étant la virgule ``,``, cette dernière ne peut pas être utilisée comme délimiteur. La convention française est d'utiliser le point virgule ``;``. Il faut pour cela utiliser le paramètre :attr:`delimiter` de la fonction :func:`~csv.reader`.
.. code-block:: python
:emphasize-lines: 2
>>> with open('synop.2015110112.csv', 'r') as f:
... r = csv.reader(f, delimiter=';')
... l = list(r)
Combien de lignes comporte le fichier ?
Combien y a t-il de données par ligne ?
.. note::
Le travail avec les fichiers ``csv`` est facilité dans Visual Studio Code par `l'extension Rainbow CSV `_. Elle s'installe à partir du menu :command:`Affichage > Extensions`.
Ce qu'il faut retenir
=====================
.. quiz:: quizz-08
:title: Les fichiers
- :quiz:`{"type":"FB","answer":"open", "size":5}` est la fonction qui permet d'ouvrir un fichier
- :quiz:`{"type":"FB","answer":"close", "size":5}` est la fonction qui permet de fermer un fichier
- La fermeture de fichier est automatique si on utilise la construction :quiz:`{"type":"FB","answer":"with", "size":5}`
- :quiz:`{"type":"TF","answer":"T"}` On peut ouvrir un fichier en ne précisant que son nom
- :quiz:`{"type":"TF","answer":"F"}` Si on ne le précise pas le mode d'ouverture par défaut d'un fichier est en écriture
- :quiz:`{"type":"TF","answer":"T"}` Lorsqu'on ouvre un fichier, c'est une bonne pratique de préciser son mode d'ouverture, lecture ou écriture
- :quiz:`{"type":"TF","answer":"T"}` Si on ne le précise pas l'encodage par défaut lors de l'ouverture d'un fichier est celui du terminal
- :quiz:`{"type":"TF","answer":"T"}` Lorsqu'on ouvre un fichier, c'est une bonne pratique de préciser son encodage
- :quiz:`{"type":"FB","answer":"read readline readlines","flags":"sequence"}` sont les 3 méthodes de lecture d'un fichier texte
- :quiz:`{"type":"TF","answer":"T"}` La méthode :meth:`~io.IOBase.read` retourne une chaine de caractères
- :quiz:`{"type":"TF","answer":"T"}` La méthode :meth:`~io.IOBase.readlines` retourne une liste de chaine de caractères
- :quiz:`{"type":"FB","answer":"write writelines","flags":"sequence"}` sont les 2 méthodes d'écriture dans un fichier texte
- :quiz:`{"type":"TF","answer":"T"}` On peut itérer sur l'objet retourné par la fonction :func:`open`
- :quiz:`{"type":"TF","answer":"T"}` Un fichier ``csv`` est un fichier texte
- :quiz:`{"type":"TF","answer":"F"}` Un fichier ``csv`` ne peut être lu qu'avec le module :mod:`csv`
- :quiz:`{"type":"TF","answer":"T"}` Le module :mod:`csv` facilite la lecture d'un fichier ``csv``
- :quiz:`{"type":"TF","answer":"F"}` La première ligne d'un fichier ``csv`` comporte des données
- :quiz:`{"type":"TF","answer":"F"}` Le délimiteur d'un fichier ``csv`` est toujours le caractère ``,``
- :quiz:`{"type":"TF","answer":"F"}` Le délimiteur d'un fichier ``csv`` est toujours le caractère ``;``
- :quiz:`{"type":"TF","answer":"T"}` On peut préciser le délimiteur d'un fichier ``csv`` à la lecture