411 lines
14 KiB
Markdown
411 lines
14 KiB
Markdown
---
|
||
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 :
|
||
|
||
```bash {.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["<texte de la colonne d'en-tête>"])
|
||
```
|
||
|
||
----
|
||
|
||
### 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`
|