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