Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Licence CC BY-NC-ND Thierry Parmentelat & Arnaud Legout Inria - UCA

Complément - niveau basique

Voici quelques utilisations habituelles du type fichier en Python.

Avec un context manager

Nous avons vu dans la vidéo les mécanismes de base sur les fichiers. Nous avons vu notamment qu’il est important de bien fermer un fichier après usage. On a vu aussi qu’il est recommandé de toujours utiliser l’instruction with et de contrôler son encodage. Il est donc recommandé de faire :

# avec un `with' on garantit la fermeture du fichier
with open("foo.txt", "w", encoding='utf-8') as sortie:
    for i in range(2):
        sortie.write(f"{i}\n")

Les modes d’ouverture

Les modes d’ouverture les plus utilisés sont :

Voici par exemple comment on pourrait ajouter deux lignes de texte dans le fichier foo.txt qui contient, à ce stade du notebook, deux entiers :

# on ouvre le fichier en mode 'a' comme append (= ajouter)
with open("foo.txt", "a", encoding='utf-8') as sortie:
    for i in range(100, 102):
        sortie.write(f"{i}\n")
The history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.
# maintenant on regarde ce que contient le fichier
# remarquez que sans 'mode', on ouvre en lecture seule
with open("foo.txt", encoding='utf-8') as entree: 
    for line in entree:
        # line contient déjà un retour à la ligne
        print(line, end='')
0
1
100
101

Il existe de nombreuses variantes au mode d’ouverture, pour par exemple :

Ces variantes sont décrites dans la section sur la fonction built-in open dans la documentation Python.

Complément - niveau intermédiaire

Un fichier est un itérateur

Nous reparlerons des notions d’itérable et d’itérateur dans les semaines suivantes. Pour l’instant, on peut dire qu’un fichier - qui donc est itérable puisqu’on peut le lire par une boucle for - est aussi son propre itérateur. Cela implique que l’on ne peut le parcourir qu’une fois dans une boucle for. Pour le reparcourir, il faut le fermer et l’ouvrir de nouveau.

# un fichier est son propre itérateur
with open("foo.txt", encoding='utf-8') as entree:
    print(entree.__iter__() is entree)
True

Par conséquent, écrire deux boucles for imbriquées sur le même objet fichier ne fonctionnerait pas comme on pourrait s’y attendre.

# Si l'on essaie d'écrire deux boucles imbriquées
# sur le même objet fichier, le résultat est inattendu
with open("foo.txt", encoding='utf-8') as entree:
    for l1 in entree:
        # on enlève les fins de ligne
        l1 = l1.strip()
        for l2 in entree:
            # on enlève les fins de ligne
            l2 = l2.strip()
            print(l1, "x", l2)
0 x 1
0 x 100
0 x 101

Complément - niveau avancé

Autres méthodes

Vous pouvez également accéder à des fonctions de beaucoup plus bas niveau, notamment celle fournies directement par le système d’exploitation ; nous allons en décrire deux parmi les plus utiles.

Digression - repr()

Comme nous allons utiliser maintenant des outils d’assez bas niveau pour lire du texte, pour examiner ce texte nous allons utiliser la fonction repr(), et voici pourquoi :

# construisons à la main une chaîne qui contient deux lignes
lines = "abc" + "\n" + "def"  + "\n"
# si on l'imprime on voit bien les retours à la ligne
# d'ailleurs on sait qu'il n'est pas utile
# d'ajouter un retour à la ligne à la fin
print(lines, end="")
abc
def
# vérifions que repr() nous permet de bien
# voir le contenu de cette chaine
print(repr(lines))
'abc\ndef\n'

Lire un contenu - bas niveau

Revenons aux fichiers ; la méthode read() permet de lire dans le fichier un buffer d’une certaine taille :

# read() retourne TOUT le contenu
# ne pas utiliser avec de très gros fichiers bien sûr

# une autre façon de montrer tout le contenu du fichier
with open("foo.txt", encoding='utf-8') as entree:
    full_contents = entree.read()
    print(f"Contenu complet\n{full_contents}", end="")
Contenu complet
0
1
100
101
# lire dans le fichier deux blocs de quatre caractères
with open("foo.txt", encoding='utf-8') as entree:
    for bloc in range(2):
        print(f"Bloc {bloc} >>{repr(entree.read(4))}<<")
Bloc 0 >>'0\n1\n'<<
Bloc 1 >>'100\n'<<

On voit donc que chaque bloc contient bien quatre caractères en comptant les sauts de ligne :

bloc #contenu
0un 0, un newline, un 1, un newline
1un 1, deux 0, un newline

La méthode flush

Les entrées-sorties sur fichier sont bien souvent bufferisées par le système d’exploitation. Cela signifie qu’un appel à write ne provoque pas forcément une écriture immédiate, car pour des raisons de performance on attend d’avoir suffisamment de matière avant d’écrire sur le disque.

Il y a des cas où ce comportement peut s’avérer gênant, et où on a besoin d’écrire immédiatement (et donc de vider le buffer), et c’est le propos de la méthode flush.

Fichiers textuels et fichiers binaires

De la même façon que le langage propose les deux types str et bytes, il est possible d’ouvrir un fichier en mode textuel ou en mode binaire.

Les fichiers que nous avons vus jusqu’ici étaient ouverts en mode textuel (c’est le défaut), et c’est pourquoi nous avons interagi avec eux avec des objets de type str :

# un fichier ouvert en mode textuel nous donne des str
with open('foo.txt', encoding='utf-8') as strfile:
    for line in strfile:
        print("on a lu un objet de type", type(line))
on a lu un objet de type <class 'str'>
on a lu un objet de type <class 'str'>
on a lu un objet de type <class 'str'>
on a lu un objet de type <class 'str'>

Lorsque ce n’est pas le comportement souhaité, on peut :

Pour illustrer ce trait, nous allons :

  1. créer un fichier en mode texte, et y insérer du texte en UTF-8 ;

  2. relire le fichier en mode binaire, et retrouver le codage des différents caractères.

# phase 1 : on écrit un fichier avec du texte en UTF-8
# on ouvre donc le fichier en mode texte
# en toute rigueur il faut préciser l'encodage,
# si on ne le fait pas il sera déterminé
# à partir de vos réglages système
with open('strbytes', 'w', encoding='utf-8') as output:
    output.write("déjà l'été\n")
# phase 2: on ouvre le fichier en mode binaire
with open('strbytes', 'rb') as bytesfile:
    # on lit tout le contenu
    octets = bytesfile.read()
    # qui est de type bytes
    print("on a lu un objet de type", type(octets))
    # si on regarde chaque octet un par un
    for i, octet in enumerate(octets):
        print(f"{i} → {repr(chr(octet))} [{hex(octet)}]")
on a lu un objet de type <class 'bytes'>
0 → 'd' [0x64]
1 → 'Ã' [0xc3]
2 → '©' [0xa9]
3 → 'j' [0x6a]
4 → 'Ã' [0xc3]
5 → '\xa0' [0xa0]
6 → ' ' [0x20]
7 → 'l' [0x6c]
8 → "'" [0x27]
9 → 'Ã' [0xc3]
10 → '©' [0xa9]
11 → 't' [0x74]
12 → 'Ã' [0xc3]
13 → '©' [0xa9]
14 → '\n' [0xa]

Vous retrouvez ainsi le fait que l’unique caractère Unicode é a été encodé par UTF-8 sous la forme de deux octets de code hexadécimal 0xc3 et 0xa9.

Vous pouvez également consulter ce site qui visualise l’encodage UTF-8, avec notre séquence d’entrée :

https://mothereff.in/utf-8#déjà l'été

# on peut comparer le nombre d'octets et le nombre de caractères
with open('strbytes', encoding='utf-8') as textfile:
    print(f"en mode texte, {len(textfile.read())} caractères")
with open('strbytes', 'rb') as binfile:
    print(f"en mode binaire, {len(binfile.read())} octets")
en mode texte, 11 caractères
en mode binaire, 15 octets

Ce qui correspond au fait que nos quatre caractères non-ASCII (3 x é et 1 x à) sont tous encodés par UTF-8 comme deux octets, comme vous pouvez vous en assurer ici pour é et là pour à.

Pour en savoir plus

Pour une description exhaustive vous pouvez vous reporter :