UKOnline

Fichier texte

Un fichier texte est composé d'une séquence de caractères. Parmi ceux-ci, on retrouve le caractère de saut de ligne et on peut donc également voir un fichier texte comme une séquence de lignes.

Comme dit précédemment, un fichier texte n'est qu'un fichier binaire. Les bits stockés dans le fichier sont en fait lus par blocs qui sont ensuite interprétés comme des caractères. Cette traduction se fait en suivant l'encodage qui a été utilisé pour sauvegarder le fichier. Python travaille par défaut avec l'encodage UTF-8, où chaque caractère par blocs de 8~bits. Ce principe est détaillé plus loin dans cette section.

Ouverture et fermeture

L'ouverture d'un fichier se fait à l'aide de la fonction prédéfinie open. Il suffit de lui passer en paramètre le chemin du fichier à ouvrir. La fonction renvoie un identifiant vers le fichier ouvert, que l'on pourra ensuite utiliser pour lire et écrire dans le fichier. Deux exceptions peuvent survenir lors de l'ouverture d'un fichier, FileNotFoundError si le fichier n'a pas été trouvé et IOError pour les autres erreurs.

L'exemple suivant tente d'ouvrir le fichier data.txt et affiche l'identifiant renvoyé par la fonction open, avant de le refermer :

L'exécution du programme affiche le résultat suivant, qui dévoile plusieurs informations par rapport au fichier ouvert :

<_io.TextIOWrapper name='data.txt' mode='r' encoding='UTF-8'>

La variable file contient donc une référence vers un objet de type io.TextIOWrapper. On peut voir trois attributs de cet objet :

  • le nom du fichier ouvert est data.txt (name) ;
  • le fichier est ouvert en mode lecture seule (mode) ;
  • et enfin, l'encodage utilisé est UTF-8 (encoding).

Une fois que l'on a fini avec le fichier, il faut le fermer pour libérer les ressources qui ont été allouées en mémoire pour son traitement. Pour cela, on fait simplement appel à la méthode close.

Mode d'ouverture

Par défaut, un fichier texte est ouvert en lecture seule, c'est-à-dire que seules les opérations permettant de lire son contenu sont autorisées. D'autres modes d'ouverture sont possibles, et peuvent être spécifié comme deuxième paramètre de la fonction open. Un mode est décrit par un ou plusieurs caractères ayant chacun une signification particulière, repris dans le tableau de la figure 2.

Caractère Description
r Lecture (par défaut)
w Écriture (avec remise à zéro)
x Création exclusive (erreur si fichier déjà existant)
a Écriture (avec ajout à la fin)
b Mode binaire
t Mode texte (par défaut)
Le mode d'ouverture d'un fichier se décrit à l'aide d'un ou plusieurs caractères, définissant les opérations autorisées sur le fichier.

Par défaut, le mode d'ouverture d'un fichier est rt, à savoir que l'accès se fait en mode texte et en lecture seule. Pour écrire dans un fichier, on utilisera le mode w ou a. Dans les deux cas, le fichier ouvert est créé s'il n'existe pas. Si le fichier existe, son contenu est complètement effacé dans le premier cas, et l'écriture démarre à la fin du contenu dans le second cas. Enfin, le mode x permet de générer une erreur de type FileExistsError si le fichier existe déjà.

Lecture

Pour lire un fichier texte, préalablement ouvert, plusieurs méthodes sont utilisables. Le plus facile consiste à utiliser la méthode read qui va lire l'intégralité du fichier et renvoyer son contenu sous forme d'une chaine de caractères. Lors de la lecture, une exception de type IOError peut se produire, si le disque est corrompu ou s'il est déconnecté pendant la lecture, par exemple. Le programme suivant affiche l'intégralité du contenu du fichier data.txt :

Malgré que cette méthode est très pratique, elle n'est pas forcément recommandée, surtout lorsque le fichier à lire est gros. En effet, la totalité du fichier va être lue depuis le disque et être placée dans une variable Python, ce qui va consommer de la mémoire.

Lecture ligne par ligne

Parfois, on souhaite pouvoir lire les lignes d'un fichier séparément. Une fois le fichier lu intégralement, on pourrait parcourir la chaine de caractères ainsi obtenue et identifier les lignes en repérant les caractères de retour à la ligne (\n sous Linux et MacOS et \r\n sous Windows).

Pour faciliter les choses, il existe une méthode readlines qui renvoie une liste de chaines de caractères, dont chaque élément correspond à une ligne. Les instructions suivantes ouvrent le fichier data.txt en lecture seule pour récupérer son contenu comme une liste de lignes qui sont ensuite affichées à l'aide d'une boucle for :

Si on suppose que le fichier data.txt possède cinq lignes, chacune contenant juste un chiffre allant de $1$ à $5$, le résultat de l'exécution du programme serait le suivant :

1

2

3

4

5

Comme vous le constatez, il y a une série de lignes vides dans l'affichage produit. La méthode readlines ne supprime en fait pas le caractère de fin de ligne dans le résultat qu'elle renvoie. Si on ne veut pas de ce ou ces caractères de fin de ligne, il faut s'en débarrasser manuellement. Pour ce faire, on peut, par exemple, utiliser la méthode rstrip des chaines de caractères qui permet de supprimer tous les caractères blancs (espace, tabulation, retour à la ligne...) qui se trouvent à la droite d'une chaine de caractères. Il suffit donc de remplacer la boucle for par :

Itérateur de lignes

Avec la méthode readlines, on a le même problème qu'avec read, à savoir que l'intégralité du fichier est lue en une fois, et doit donc être stockée en mémoire. Une autre solution consiste à utiliser un itérateur sur le fichier ouvert, que l'on va pouvoir parcourir à l'aide d'une boucle for. De nouveau, le ou les caractères de retour à la ligne seront inclus et doivent être supprimés avec rstrip si on n'en veut pas. Voici la nouvelle version de la lecture du fichier data.txt :

Le premier avantage est que le code est beaucoup plus compact et lisible. Le deuxième avantage est que le fichier est lu au fur et à mesure sur le disque, et pas intégralement comme avec les deux solutions précédentes.

Écriture

Voyons maintenant comment écrire un fichier texte. L'ouverture et la fermeture du fichier fonctionnent exactement de la même manière que pour la lecture, si ce n'est que le mode doit inclure w ou a. De nouveau, une exception de type IOError peut se produire, si le disque devient plein pendant l'écriture, par exemple.

Pour écrire dans un fichier, on utilise la méthode write qui ajoute la chaine de caractères reçue en paramètre dans le fichier. L'exemple suivant écrit la table de multiplication de $7$ dans le fichier data.txt :

On ouvre d'abord le fichier en mode écriture avec w. On écrit ensuite une première ligne avant de rentrer dans une boucle pour afficher les lignes de la table de multiplication. Remarquez que l'on doit manuellement ajouter le retour à la ligne dans la chaine de caractères envoyée à la fonction write. Voici le fichier data.txt produit par ce programme :

Table de 7 :
0 x 7 = 0
1 x 7 = 7
2 x 7 = 14
3 x 7 = 21
4 x 7 = 28
5 x 7 = 35
6 x 7 = 42
7 x 7 = 49
8 x 7 = 56
9 x 7 = 63

Quelques remarques sont à soulever par rapport à l'écriture dans un fichier texte :

  • Lorsqu'on fait une écriture de fichier, sa fermeture à la fin des opérations avec la méthode close est très importante. En effet, sans cet appel, il se peut que les données ne soient pas écrites sur le disque, mais uniquement dans la mémoire tampon du système d'exploitation. Le fichier créé sera donc vide sur le disque.
  • Avec le mode w, si le fichier existe, son contenu est complètement effacé et l'écriture commence à son début. Pour éviter cela, il faut soit utiliser le mode a, dans lequel l'écriture commence après le contenu actuel du fichier s'il existe déjà, soit utiliser le mode x qui provoque une erreur de type FileExistsError si le fichier existe déjà, ou enfin tester avant si le fichier existe ou non, avec la fonction exists du module os.path.
  • Enfin, si on utilise le caractère \n pour la fin de ligne, elles ne seront pas forcément valables sur tous les systèmes d'exploitation. Le ou les caractères qui identifient une fin de ligne se trouvent dans la variable globale os.linesep du module os.

Encodage

En informatique, tout caractère est en fait associé à un identifiant numérique qui est typiquement un nombre entier. Cette correspondance est établie dans une table de caractères. Par exemple, la table de caractères ASCII ou iso-646 (l'American Standard Code for Information Interchange (ASCII) a été développé pour représenter les caractères anglais) contient un total de $128$ caractères et est utilisée pour représenter les caractères anglais. Cette dernière est reprise à la figure 3, où l'identifiant numérique de chaque caractère est obtenu en additionnant le produit du numéro de sa ligne par $16$ avec le numéro de sa colonne (sachant que $A = 10$, $B = 11$...).

Encodage US ASCII 7 bits
La table de caractères ASCII (iso-646) comporte $128$ caractères utilisés pour représenter les caractères anglais.

Deux fonctions prédéfinies permettent d'effectuer la conversion entre un caractère et son identifiant numérique correspondant. La fonction ord donne l'identifiant numérique correspondant à un caractère tandis que la fonction chr fait l'opération inverse. Examinons l'exemple suivant :

Le caractère | doit avoir comme code $7 \times 16 + 12 = 124$ et le caractère dont le code est $65 = 4 \times 16 + 1$ est le A. Ces calculs sont confirmés par le résultat de l'exécution des deux instructions :

124
A

À la table de caractères, il faut associer une méthode d'encodage, c'est-à-dire une manière de représenter chaque identifiant numérique en une suite de bits. Pour l'ASCII, ce n'est pas très compliqué, il suffit de $7$ bits pour chaque caractère afin de pouvoir tous les représenter et c'est ce qui est fait.

Unicode

Python fonctionne avec la table de caractères Unicode (ISO 10646), un standard d'échange de texte qui a vu le jour en 1987. Cette table comporte plus de $128000$ caractères (la liste de tous les caractères Unicode peut être consultée sur le site web suivant : http://unicode-table.com/) dans sa dernière version.

De plus, Python utilise l'encodage UTF-8 pour représenter les caractères Unicode, dans lequel l'unité de base sont des blocs de $8$ bits. Cet encodage est compatible avec l'ASCII, c'est-à-dire que ses $128$ caractères ont le même identifiant numérique.

Comme le montre la figure 4, Unicode contient beaucoup de caractères, permettant ainsi de couvrir toutes les langues du monde et encore plus, comme les emojis, par exemple.

Caractères Unicode
La table de caractères Unicode comporte plus de $128000$ caractères, couvrant toutes les langues du monde et encore plus.

Séquence d'échappement

Si on souhaite insérer un caractère Unicode dans une chaine de caractères, sans savoir le taper directement au clavier, on peut utiliser une séquence d'échappement comme on a vu au chapitre 2.

Pour insérer un caractère Unicode, il suffit d'écrire \u ou \U suivi de l'identifiant numérique du caractère voulu. La différence entre les deux notations est que la première se limite aux identifiants représentables sur 16 bits alors que la seconde permet d'aller jusque 32 bits.

Par exemple, pour écrire le symbole de l'intégrale double et l'emoji étonné/effrayé, il suffit d'écrire les instructions suivantes :

Choix de l'encodage

Lorsqu'on lit ou écrit un fichier texte dont l'encodage n'est pas UTF-8, il faut le préciser lors de l'ouverture à l'aide du paramètre nommé encoding. Évidemment, on sera du coup limité par rapport aux caractères que l'on pourra lire et écrire.

Par exemple, le programme suivant provoque une erreur d'exécution car on tente d'écrire un caractère n'étant pas dans la table ASCII alors que le fichier a été créé suivant cet encodage :

Traceback (most recent call last):
  File "program.py", line 3, in <module>
    file.write('€')
UnicodeEncodeError: 'ascii' codec can't encode character '\u20ac' in position 0: ordinal not in range(128)