# Bases de Python - Partie 5 - Fichiers

Travailler avec des fichiers permet de stocker, récupérer ou manipuler des données en dehors du programme. Les fichiers peuvent être binaires ou composés de texte.

## Ouvrir un fichier

En Python, la fonction principale pour ouvrir un fichier est `open()`. Sa syntaxe générale est :

```python
fichier = open('nom_du_fichier.txt', 'mode', encoding='utf-8')
```

- `'nom_du_fichier.txt'` : nom ou chemin du fichier.
- `'mode'` : représentation de l’action (voir ci-dessous).
- `encoding='utf-8'` : gestion des caractères spéciaux, souvent essentiel en français. Si on ne précise pas, ce sera utf-8 par défaut, ce qui est recommandé.


### Les modes d’ouverture de fichier

Le mode d'ouverture permet de savoir quelles actions vous souhaitez (et pourrez) faire avec votre fichier.

- `'r'` : lecture seule (**r**ead only) : on démarre au début du fichier, erreur si le fichier n'existe pas
- `'w'` : écriture (**w**rite) : on démarre au début du fichier, écrase le contenu, crée le fichier au besoin
- `'a'` : ajout (**a**ppend) : on démarre à la fin du fichier, crée le fichier si besoin
- `'x'` : création uniquement (e**x**clusive) : échoue si le fichier existe déjà
- les versions `+` (ex.: `'r+'`, `'w+'`, `'a+'`, `'x+'`) ajoutent la possibilité de lire et écrire simultanément dans le fichier.

```admonish example title="Exemple"
~~~python
fichier = open('exemple.txt', 'r', encoding='utf-8')
~~~
```

#### Mode binaire

Les modes listés au dessus ouvrent les fichiers par défaut en **mode texte**. Dans ce cas, Python interprète les données comme des chaînes de caractères (`str`) et gère la conversion d'encodage (par exemple, `utf-8`).

Pour manipuler des **données brutes** (images, fichiers audio, etc.), il faut utiliser le **mode binaire** en ajoutant le caractère `'b'` :

- `'rb'` : lecture binaire
- `'wb'` : écriture binaire
- `'ab'` : ajout binaire
- combiné avec `+` pour lecture/écriture binaire, comme `'rb+'`, `'wb+'`, `'ab+'`

En mode binaire, Python lit et écrit des objets de type `bytes` et ne fait aucune conversion d’encodage ni adaptation de fin de ligne : les octets lus et écrits sont strictement identiques à ceux stockés sur le disque.


## Fermer un fichier

Il est impératif de fermer un fichier après utilisation pour libérer les ressources du système. Si vous ne le faites pas vous vous exposez à des erreurs générées par le système d'exploitation (par exemple, trop de fichiers ouverts, ou fichier impossible à ouvrir car déjà utilisé ailleurs)

```python
fichier.close()
```

Une meilleure méthode consiste à utiliser le bloc `with` (qu'on appelle **gestionnaire de contexte**), qui gère automatiquement la fermeture:

```python
with open('exemple.txt', 'r', encoding='utf-8') as fichier:
   contenu = fichier.read()
```


## Lire le contenu d’un fichier

### La meilleure méthode : boucle `for`

On peut directement parcourir le fichier avec une boucle `for`, ligne par ligne. Cette méthode est la plus simple et la plus propre : elle combine les avantages de `readline` (on ne charge pas tout le fichier en mémoire) et d'une boucle `for`, tout en ayant un code très facile à lire et comprendre.

```admonish example title="Exemple"
~~~python
with open('exemple.txt', 'r', encoding='utf-8') as fichier:
    for ligne in fichier:
        print(ligne)
~~~
```

### Lire tout d’un coup (read)

La méthode `read` renvoie tout le texte comme une seule chaîne de caractères. Sur des gros fichiers, on stocke tout le contenu en mémoire. Cela peut faire crasher votre programme si vous traitez des très gros fichiers.

```admonish example title="Exemple"
~~~python
with open('exemple.txt', 'r', encoding='utf-8') as fichier:
    texte = fichier.read()
    print(texte)
~~~
```

### Lire ligne par ligne (readlines)

La méthode `readlines` renvoie une liste de lignes. Chaque ligne est une chaîne de caractères. Même inconvénient qu'au dessus, mais plus facile à utiliser.

```admonish example title="Exemple"
~~~python
with open('exemple.txt', 'r', encoding='utf-8') as fichier:
    lignes = fichier.readlines()
    for ligne in lignes:
        print(ligne)
~~~
```

### Lire chaque ligne séparément (readline)

La méthode `readline` permet de lire la prochaine ligne du fichier, et donc de lire le fichier en le traitant ligne par ligne.
On préfèrera l'utiliser sur des gros fichiers pour éviter de stocker tout le contenu dans la mémoire du programme.

```admonish example title="Exemple"
~~~python
with open('exemple.txt', 'r', encoding='utf-8') as fichier:
    ligne = None
    while ligne != "":
        ligne = fichier.readline()
        print(ligne)
~~~
```

## Écrire dans un fichier

Pour écrire, passez le mode `'w'` (ou `'a'`). 

Puis utilisez la méthode 
- `write` pour écrire toute une chaîne ou un bloc binaire
- `writelines` pour écrire une liste de lignes (attention, cette méthode n'ajoute pas automatiquement `\n` à la fin des lignes, à vous de le faire)

```admonish warning title="Attention"
Avec `'w'` le contenu précédent est écrasé.
```

```admonish example title="Exemple"
~~~python
with open('exemple.txt', 'w', encoding='utf-8') as fichier:
    fichier.write("Premier texte ajouté.\n")
    fichier.write("Une deuxième ligne.\n")
~~~
```


### Ajouter à la fin (append, mode `'a'`)

Avec le mode append, vous ouvrez le fichier en mode écriture, mais vous n'effacez pas le contenu. Toute nouvelle écriture sera joutée à la fin du fichier.

```admonish example title="Exemple"
~~~python
with open('exemple.txt', 'a', encoding='utf-8') as fichier:
    fichier.write("\nUne nouvelle ligne ajoutée.")
~~~
```

## Notion de curseur

En Python, chaque fichier ouvert possède un curseur (ou pointeur) qui indique où l’action de lecture ou d’écriture va se produire dans le fichier. 

La position initiale de ce curseur dépend du mode choisi : 
- il se place au début en lecture (mode `r`)
- à la fin pour l’ajout (mode `a`) 

Lors de la lecture ou de l’écriture, ce curseur avance automatiquement, ce qui permet de lire ou écrire à la suite.

Il est possible de manipuler manuellement la position du curseur grâce à deux méthodes principales :
- `tell`, qui retourne la position actuelle du curseur dans le fichier
- `seek(position)`, qui permet de déplacer le curseur à une position spécifique (en octets).

```admonish example title="Exemple"
~~~python
with open("exemple.txt", "r", encoding="utf-8") as fichier:
    fichier.tell() ## retourne la postion du curseur (normalement 0, vuqu'on vient juste d'ouvrir le fichier en mode "r")
    fichier.seek(0) ## Remet le curseur en position 0, c'est à dire au début du fichier. Peut servir à relire le fichier sans l'ouvrir ou le refermer 
~~~
```

## Bonnes pratiques

- Toujours privilégier `with open(...) as ...` pour la robustesse. Cela évitera d'oublier de fermer le fichier (ou en cas d'erreur de ne pas pouvoir le fermer)
- Utiliser `encoding='utf-8'`. 
- Pour les fichiers CSV, JSON ou autres formats, Python propose des modules dédiés (`csv`, `json`, ...).
