---
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["<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`
