--- title: Manipuler des fichiers texte author: Steve Kossouho --- # Découvrir comment manipuler des fichiers texte ---- ## Écrire et lire des fichiers (version de base) Python vous offre, sans import nécessaire, une fonction `open`{.python} vous permettant d'ouvrir des fichiers pour les manipuler. Cette fonction semble provenir du module `io`{.python} de la bibliothèque standard. Pour ouvrir un fichier en écriture : ```python {.numberLines} file = open("fichier.txt", "w", encoding="utf-8") # Ouvrir le fichier file.write("Texte à écrire\nDeuxième ligne.\n") file.write("Troisième ligne.") file.close() # Toujours penser à le refermer, pour votre système ``` ---- La fonction `open()`{.python} prend quelques arguments importants : - `filename` : chemin de fichier relatif (au répertoire de travail) ou absolu - `mode` : `"w"` pour write, `"r"` pour read, `"a"` pour append - `encoding`: rendez-le explicite, et "utf-8" si possible ---- ### Pages de code les plus fréquemment rencontrées : 1. `utf-8` : ±90% du temps (complètement rétrocompatible avec `ASCII`) 2. `windows-1252` ou `1252` : ±5% du temps (provient de Windows 95) 3. `latin-1` : ±5% du temps (aussi nommé `iso8859-1`) 4. `utf-16` : certains pays n'utilisant quasiment pas de caractères ASCII, ils produisent souvent de l'UTF-16 (ex. Chine, Russie) ---- ### Plus : Échappement de caractères - Échappements classiques 0 à 8 bits (_0 à 255_) - Échappements de caractères 0 à 16 bits (_256 à 65535_) - Échappements de caractères 0 à 32 bits (_65536 et plus_) ```python {.numberLines} print("\x41 = \101 = A") # codes 8 bits, 2 caractères obligatoires print("\u00E6 = æ") # codes 16 bits, 4 caractères obligatoires print("\U0001F44D = 👍️") # codes 32 bits, 8 caractères obligatoires ``` [Algorithme UTF-8](https://en.wikipedia.org/wiki/UTF-8) ---- ### Ouvrir un fichier en lecture ```python {.numberLines} file = open("fichier.txt", "r", encoding="utf-8") line1 = file.readline() # Lit une ligne, et avance le curseur à la ligne suivante rest1 = file.read() # Lit le fichier jusqu'à la fin file.close() # Toujours penser à le refermer, pour votre système ``` ---- ### Outils disponibles pour gérer des fichiers Si l'on utilise des éditeurs de texte, on pourrait penser qu'il est facile de demander à Python de, par exemple : ```{.text .numberLines} - Remplacer une ligne - Lire une ligne arbitraire d'un document - Supprimer une ligne - Remplacer du texte ``` Aucune de ces fonctionnalités n'existe en standard, dans aucun langage; les logiciels qui vous permettent ces manipulations sont obligés, souvent, de conserver une copie du document en mémoire (quand c'est possible) et d'effectuer les manipulations en mémoire avant de réécrire tout ou partie du fichier. Tout ce qu'on a d'utile, c'est : ```{.text .numberLines} - Ajouter du contenu à la fin - Réécrire le fichier - Lire ligne par ligne du début, jusqu'à arriver à une ligne arbitraire - Déplacer le curseur à une position en octets ``` ---- Une bibliothèque externe Python propose des outils pour effectuer certaines de ces actions : ```sh {.numberLines} pip install textfile ``` [Page PyPI du package textfile](https://pypi.org/project/textfile/) ---- ## Écrire et lire des fichiers (gestionnaire de contexte) Une version raccourcie du `f = open()`{.python} … `f.close()`{.python} consiste à utiliser un gestionnaire de contexte (la formation Python intermédiaire explique le concept). La syntaxe consiste en ce qui suit : ```python {.numberLines} with open("fichier.txt", "r", encoding="utf-8") as file: # `file.close()` : appelé automatiquement en fin de bloc text = file.read() ``` À la fin de l'exécution du bloc, une méthode spéciale est automatiquement appelée et ferme le descripteur de fichier s'il est resté ouvert. Plus besoin d'exécuter manuellement `file.close()`{.python} ---- ### Bonus : Ouverture simultanée de fichiers Avec un seul bloc `with` … `as`, il est possible d'utiliser plusieurs gestionnaires de contexte en même temps, par exemple, si l'on veut ouvrir un fichier en lecture, faire une conversion de données et les écrire dans un fichier ouvert en écriture, on peut : ```python {.numberLines} with open("input.txt", "r") as infile, open("output.txt", "w") as outfile: data = infile.read().upper() # tout mettre en majuscules outfile.write(data) ``` ---- ## Parcourir facilement les lignes d'un fichier Il est possible, lorsque vous avez un descripteur de fichier ouvert, de parcourir facilement ligne à ligne le contenu de votre fichier texte, en utilisant le mot-clé `for`{.python} : ```python {.numberLines} with open("fichier.ext", "r", encoding="utf-8") as file: for line in file: print(line) ``` En interne, ici la fonction `open()`{.python} est exécutée en deux temps; un première fois pour renvoyer le descripteur de fichier, et une seconde fois lors de la fin du bloc... Mais c'est un peu magique, et c'est un sujet pour une formation plus avancée de Python. ---- ## Formats structurés : JSON Le **JSON** (JavaScript Object Notation) est un format de texte extrêmement populaire pour partager des données hiérarchiques. Il a largement supplanté le XML dans les systèmes modernes et les services web. Les données contenues dans un fichier texte au format JSON peuvent être interprétées et transformées en données Python, telles que `dictionnaires` et `listes`, `nombres` etc. ---- ### Lecture et écriture de fichiers JSON Il est extrêmement facile de lire ou d'écrire du contenu depuis ou vers un fichier représentant une structure JSON. [Documentation du module JSON](https://docs.python.org/fr/3/library/json.html) ```python {.numberLines} import json # Lire un fichier JSON with open("fichier.json", "r", encoding="utf-8") as file: data = json.load(file) # Écrire un fichier JSON, l'objet à écrire doit être une liste ou # un dictionnaire with open("copie.json", "w", encoding="utf-8") as file: json.dump(data, file, indent=2) ``` ---- ## Formats structurés : CSV Le CSV est un format accessible et non propriétaire pour représenter des données tabulées (en table) . C'est un format texte, utilisant, par défaut, des virgules et des retours à la ligne pour séparer les lignes et colonnes. La manipulation des CSV fait aussi partie de la bibliothèque standard de Python. [Documentation CSV](https://docs.python.org/3/library/csv.html) ---- ### Parcourir les lignes d'un fichier CSV Lire le contenu d'un fichier CSV est presque aussi simple que pour un fichier JSON, à quelques détails près : ```python {.numberLines} import csv with open("fichier.csv", "r", encoding="utf-8") as file: reader = csv.reader(file, delimiter=",") for row in reader: print(row) # Affiche une ligne sous forme d'une liste de chaînes de caractères ``` Vous ouvrez d'abord votre fichier en lecture et en mode texte, comme tout fichier texte, puis vous créez un objet de type lecteur, configuré pour lire et interpréter le contenu de lignes du document. Par défaut, le lecteur ne reconnaît que les documents dont les cellules sont séparées par la virgule `,` et dont les lignes sont séparées par `\n`. Heureusement, cet objet peut être configuré pour comprendre des fichiers avec des propriétés différentes. ---- ### Paramètres de formatage d'un fichier CSV Pour lire ou écrire un fichier CSV, il est possible de configurer les objets en charge d'interpréter ou de formater le contenu du fichier. En lecture, vous configurerez un objet de type `reader`, et en écriture... `writer`. Dans les deux cas, la procédure sera la même. Vous avez trois façons de configurer votre objet lecteur ou écrivain : - Soit en passant un objet de type `Dialect`, un objet dont les attributs servent à configurer toutes les facettes du formatage d'un fichier CSV; - soit en passant un nom d'objet de type `Dialect`, par exemple `"excel"` (séparateur virgule); - soit en passant des arguments individuels qui correspondent aux attributs des objets `Dialect`. ---- #### Configuration via un objet Dialect Pour définir votre propre `Dialect`, vous pouvez créer une classe héritant de `Dialect`, dans laquelle vous n'avez qu'à redéfinir les attributs dont vous souhaitez changer la valeur. Voir [Classe Dialect du module CSV](https://docs.python.org/fr/3/library/csv.html#dialects-and-formatting-parameters) pour connaître les valeurs de base. ```python {.numberLines} import csv class MonDialecte(csv.Dialect): """Dialecte CSV utilisant des // comme séparateur.""" delimiter = "//" ``` ---- Une fois que votre dialecte est défini et prêt à l'usage, vous pouvez l'utiliser pour lire ou écrire un fichier : ```python {.numberLines} import csv class MonDialecte(csv.Dialect): """Dialecte CSV utilisant des // comme séparateur.""" delimiter = "//" with open("fichier.csv", "r", encoding="utf-8") as file: reader = csv.reader(file, dialect=MonDialecte()) for row in reader: print(row) ``` ---- Plutôt que de créer une classe de dialecte pour personnaliser la manipulation d'un fichier, il est possible de passer à l'argument `dialect` une chaîne de caractères représentant le nom d'un dialecte prédéfini. Par défaut, les valeurs disponibles sont `"excel"`, `"excel-tab"` et `"unix"`. ```python {.numberLines} ... reader = csv.reader(file, dialect="excel") ``` Vous pouvez aussi enregistrer des noms de dialecte supplémentaires en utilisant la fonction [`csv.register_dialect(name, dialect)`](https://docs.python.org/fr/3/library/csv.html#csv.register_dialect). ```python {.numberLines} import csv class MonDialecte(csv.Dialect): """Dialecte CSV utilisant des // comme séparateur.""" delimiter = "//" csv.register_dialect("mondialecte", MonDialecte) ``` ---- #### Configuration via les attributs de Dialect Pour gagner du temps, il est possible, lorsque vous créez votre objet lecteur ou écrivain, de passer des arguments pour préciser le format du fichier CSV à lire ou à écrire. Ces arguments ont les mêmes noms que les attributs de la classe `Dialect`, et ont le même usage. ```python {.numberLines} import csv with open("fichier.csv", "r", encoding="utf-8") as file: reader = csv.reader(file, delimiter=";", doublequotechar=True) # etc., etc. for row in reader: print(row) ``` ---- ### Détecter le dialecte d'un fichier Outre la configuration de dialecte manuelle, il est possible de demander à Python d'autodétecter les propriétés de formatage d'un fichier CSV, et de générer automatiquement un objet `Dialect`. Pour cela, vous devez utiliser la classe `csv.Sniffer` : ```python {.numberLines} import csv with open("output.csv", "r", encoding="utf-8") as file: dialect = csv.Sniffer().sniff(file.read()) # Lire le contenu du fichier pour détecter le dialecte file.seek(0) # Revenir au début du fichier pour démarrer la lecture reader = csv.reader(file, dialect=dialect) for row in reader: print(row) ``` ---- ### Exemple : Copier un fichier CSV vers un fichier cible On peut avec le module CSV lire un contenu CSV dans un objet Python, puis réécrire le contenu de cet objet dans un nouveau fichier CSV. ```python {.numberLines} import csv table = list() # Objet à utiliser pour stocker le contenu with open("fichier.csv", "r", encoding="utf-8") as file: reader = csv.reader(file, delimiter=",") for row in reader: table.append(row) # Ajouter la ligne comme élément de `table` with open("copie.csv", "w", encoding="utf-8") as file: writer = csv.writer(file, delimiter=";") writer.writerows(table) # table doit être une liste de listes ``` ---- ### Lire chaque ligne sous forme de dictionnaire Si votre fichier CSV contient des en-têtes, et que vous préférez les utiliser pour retrouver le contenu d'une colonne, plutôt qu'utiliser son indice, vous pouvez procéder à [l'utilisation d'un `DictReader`](https://docs.python.org/fr/3/library/csv.html#csv.DictReader) : ```python {.numberLines} import csv with open("example-file.csv", "r", encoding="utf-8") as csvfile: reader = csv.DictReader(csvfile) # lit automatiquement l'en-tête pour définir les clés des dictionnaires for row in reader: # lit les lignes suivantes, renvoyées sous forme de dictionnaires print(row) print(row[""]) ``` ---- ### Lire un CSV sans en-tête via dictionnaires Si votre fichier CSV ne contient pas de ligne d'en-tête, mais que vous souhaitez quand même définir des en-têtes à associer aux colonnes, vous pouvez utiliser un `DictReader` et préciser un argument `fieldnames=` : ```python {.numberLines} import csv with open("example-file.csv", "r", encoding="utf-8") as csvfile: reader = csv.DictReader(csvfile, fieldnames=["col1", "col2"]) # définit les en-têtes for row in reader: # lit les lignes suivantes, renvoyées sous forme de dictionnaires print(row) # renvoie un dictionnaire avec des clés "col1" et "col2" print(row["col1"]) ``` ---- ### Écrire un fichier CSV ```python {.numberLines} import csv # Créer une liste de listes pour imiter une feuille de calcul rows = [] for i in range(10): rows.append([str(j + i) for j in range(10)]) # Sous Windows, il faut préciser un argument newline à la fonction open, sinon # chaque ajout de ligne causera deux passages à la ligne with open("output.csv", "w", encoding="utf-8", newline="") as file: writer = csv.writer(file, delimiter=";") writer.writerows(rows) ``` ---- ### Écrire un CSV avec des en-têtes ```python {.numberLines} headers = ["nom", "prenom"] content = [ {"nom": "Doe", "prenom": "John"}, {"nom": "Smith", "prenom": "Jane"} ] with open("header-output.csv", "w", encoding="utf-8") as file: # Initialiser le writer en précisant la liste des textes d"'en-tête writer = csv.DictWriter(file, fieldnames=headers) # Écrire la ligne d'en-tête writer.writeheader() # Écrire le contenu restant writer.writerows(content) ``` Exemple avec un `DictWriter`