Looking for Computer Science  & Information Technology online courses ?
Check my new web site: https://www.yesik.it !

Il y a quelque (argh!) années, j'ai écrit l'article Lire et écrire un fichier texte avec Python. Au vu des statistiques de consultation de cette page, le me suis dit qu'il était peut-être temps d'écrire son pendant, à savoir comme lire et écrire un fichier binaire avec Python. En effet, même si la tradition Unix est de privilégier le format texte, dans la pratique on est souvent confronté à des données binaires. Ce sera notamment incontournable dès qu'il s’agira de traiter des images ou du son. Ou encore lorsqu'il faudra extraire des données d'un fichier au format propriétaire généré par un logiciel commercial. C'est justement dans ce contexte que j'écris cet article, puisque je vais travailler sur un fichier de données historiques généré par le logiciel associé à une station météo Heavyweather WS3600.

Disponible sur GitHub:

L'ensemble des sources de cet article ainsi que les fichiers de données qui lui servent de support sont disponibles sur GitHub dans le projet s-leroux/HEAVYWEATHER-WS3600.

À votre convenance vous pouvez:

Pour commencer

C'est quoi, un fichier binaire?

Pour faire une réponse de Normand, tout fichier qui n'est pas un fichier texte est un fichier binaire.

Plus pragmatiquement, un fichier texte est fichier dans lequel les données sont stockées sous une forme humainement lisible qui permet leur consultation où leur modification à l'aide d'un éditeur de texte. Cela facilite l'accès au contenu du fichier par un utilisateur et améliore l'interopérabilité. En contrepartie, cette représentation textuelle impose généralement un surcoût de travail pour l'interprétation des données par le programme de traitement.

Cela s'oppose donc à la notion de fichier binaire où les données sont enregistrées dans un format proche de celui que peut directement manipuler la machine – par exemple en privilégiant le même encodage binaire pour les nombres que celui que peut manipuler le microprocesseur. Adopter cette représentation peut améliorer les performances de traitement et parfois rendre les fichiers plus compacts. Sous un aspect moins technique, l'utilisation d'un fichier binaire est parfois aussi employé comme technique d’offuscation par les éditeurs de logiciel afin de rendre leurs produits indispensables pour l'exploitation des données.

Remarque:

C'est une assez juste définition si l'on considère des programmes écrits dans des langage de relativement bas niveau comme le C. Dans ce cas, la représentation binaire des données dans le fichier est très proche du format des données en mémoire lorsque le programme les manipule.

Pour les langages de plus haut niveau, ou les langages de scripts dont Python fait parti, les choses sont à relativiser puisque que vous constaterez que le traitement d'un fichier binaire semble plus compliqué que celui d'un fichier texte. Cela peut paraitre paradoxal, mais c'est parce qu'en réalité la majeure partie du travail relatif à la manipulation de données textuelles est pris en en charge par Python ou ses bibliothèques, ce qui en cache la complexité au programmeur.

Binary or text file format.png

Encodage binaire ou texte — Les mêmes données (ici, la vitesse du vent et sa direction dans un fichier de données météorologiques) peuvent être encodées sous forme binaire ou sous forme textuelle.

L'encodage binaire utilise une représentation proche de la machine. Par exemple, en encodant les nombres à virgule flottante en suivant la norme IEEE 784, identique à celle utilisée par le microprocesseur. Les différentes données stockées peuvent utiliser des encodages différent selon celui qui est le plus adapté. L'exploitation des données binaires nécessite un logiciel spécialisé.

Avec l'encodage texte on décompose la représentation textuelle ("humainement lisible") des données en caractères qui sont tous encodés dans le fichier en utilisant un même jeu de caractère comme ISO/IEC 8859-1 ou ASCII. Les données peuvent être examinées ou modifiées avec un éditeur de texte ou n'importe quel autre logiciel capable de relire le jeu de caractère utilisé.



Le fichier support

Comme je le disais en introduction, le fichier qui me sert de support est celui généré par une station météo de type WS3600 de la marque Heavy Weather. Plus précisément, je vais illustrer cet article avec le fichier de test test_data.dat disponible en téléchargement dans la section en français du site du fabriquant. Cela vous permettra de refaire toutes les manipulations en utilisant les mêmes données que moi:

sh$ mkdir ws3600
sh$ cd ws3600
sh$ wget http://www.heavyweather.info/new_french/3600history/test_data.dat

# Le fichier généré par le "vrai" logiciel d'exploitation d'une
# station WS3600 porte le nom 'history.dat' 
sh$ mv test_data.dat history.dat

Lecture octet par octet

Je ne sais pas si la définition que je donnais plus haut était assez claire, mais ce qu'il faut comprendre, c'est qu'un fichier, quel qu'il soit, est une suite d'octet. C'est l'interprétation de ces octets qui ne sera pas la même entre un fichier texte et un fichier binaire.

Lire un fichier binaire en Python peut donc se faire avec les mêmes fonctions que que pour un fichier texte. À savoir avec open, read et close, comme vous allez le voir dans l'exemple ci-dessous où je lis un fichier octet par octet:

# the 'b' modifier is used to prevent erroneous
# conversion of end-of-line characters on some platforms.
f = open("history.dat", "rb")
 
try:
    while True:
	bytes = f.read(1) # read the next /byte/
	if bytes == "":
	    break;
	# Do stuff with byte
	# e.g.: print as hex
	print "%02X " % ord(bytes[0]) 
 
except IOError:
	# Your error handling here
	# Nothing for this example
	pass
finally:
    f.close()

rb

La seule différence avec la lecture octet par octet d'un fichier texte est l'utilisation du modificateur b lors de l'appel à open:

f = open("history.dat", "rb")

Ce b indique à Python que je veux relire le fichier tel quel – sans conversion automagique des caractères fin de ligne pour les adapter à la plate-forme où le programme s'exécute.

Si vous testez l'affichage produit par ce programme avec celui de la commande standard hexdump(1), vous verrez que le fichier a bel et bien été lu:

# Mon programme
sh$ python simple-read.py | head -16 | sed '{N;N;N;s/\n/ /g}'
55  55  55  55 
7B  C7  E2  40 
66  66  78  44 
66  66  7E  44 
# Avec hexdump(1)
sh$ hexdump -v -e '4/1 "%02X  ""\n"' < history.dat | head -4
55  55  55  55 
7B  C7  E2  40 
66  66  78  44 
66  66  7E  44 

D'accord, mais lire le fichier, ce n'est rien: ce qui est réellement utile, c'est d'en interpréter le contenu. C'est à dire de pouvoir décoder les données binaires qui y sont stockées. Or cela impose de savoir comment sont organisées ces données et sous quel forme elles sont encodées. Si le format de fichier binaire est un format standard, vous avez de la chance: ces informations sont documentées. Si c'est un format bien connu, il est probable que vous trouviez les informations nécessaires sur Internet. Si c'est un obscure format propriétaire, il ne vous reste plus qu'à faire du reverse engineering avant de pouvoir aller plus loin …

Décodage du fichier

Format du fichier d'exemple

Avec le support qui me sert d'exemple, j'ai été assez chanceux. En effet, bien qu'il s'agisse d'un format propriétaire et non documenté officiellement, on trouve sur internet les informations nécessaires à l'exploitation des fichiers binaires générés par la station météo WS3600 [1]:

offset(octet) type C Description Unité
00double[8]DateJour fractionnaire depuis le 30 dec 1899 00:00:00 (GMT)
08float[4]Pression atmosphérique (abs)millibars
12float[4]Pression atmosphérique (rel)millibars
16float[4]Vitesse du ventm/s
20long[4]Direction du vent0-15 gradué à partir de Nord (0) dans le sens horaire: N, NNE, NE, ENE, …
24float[4]Vitesse du vent (rafale)m/s
28float[4]Précipitations (totales)millimètres
32float[4]Précipitations (nouvelles)millimètres
36float[4]Température sous abris°Celsius
40float[4]Température extérieure°Celsius
44float[4]Humidité sous abris%
48float[4]Humidité extérieure%
52long (?)[4]???toujours 0

Ce format de fichier ne va pas être trop compliqué à décoder puisque chaque enregistrement fait toujours 56 octet. D'un point de vue plus pédagogique, vous remarquerez que trois encodages différents sont utilisés pour les nombres:

double
nombre à virgule flottante encodé sur 8 octets
float
nombre à virgule flottante encodé sur 4 octets
long
entier encodé sur 4 octets

Dernier point à noter, l'ordre des octets dans ces nombres suit la convention little-endian – ce qui se comprend facilement pour des données destinées par le fabriquant à être exploitées sur une plate-forme Windows sous architectures x86 et x86-64 (ceci dit, j'imagine que le micro-contrôleur embarqué dans la station météo est lui aussi little-endian).

Lire enregistrement par enregistrement

Maintenant que nous avons une idée claire du format de fichier, passons à son décodage par Python. Comme souvent, il existe une bibliothèque standard pour nous aider. Bien entendu, pas pour décoder ce format particulier, mais pour les formats binaires en général. Il s'agit du module struct. Je peux donc déjà modifier quelque peu le programme original:

import struct
 
f = open("history.dat", "rb")
 
try:
    while True:
        record = f.read(56)
        if len(record) != 56:
            break;
        # Do stuff with record
 
except IOError:
        # Your error handling here
        # Nothing for this example
        pass
finally:
    f.close()

Notez les changements dans le code par rapport à l'itération précédente:

Si vous le souhaitez, vous pouvez toujours tester ce programme en état, mais puisque je ne fais rien de l'enregistrement lu, aucun affichage ne sera produit. En effet, avant de pouvoir effectuer le moindre traitement, il faut décoder chaque enregistrement.

Décoder un enregistrement

format type C
équivalent
encodage
 ddoubleIEEE 754 binary64
 ffloatIEEE 754 binary32
 llong32 bits complément à 2

Le module Python struct est spécialement conçu pour décoder (et encoder) des données binaires. La clé du fonctionnement de ce module est d'utiliser un format pour décrire la structure d'un enregistrement sous la forme d'une chaîne de caractères Python. Dans ce format, chaque type de donnée correspond à une lettre. La documentation officielle donne l'ensemble des codes pour le format. À titre d'exemple, le tableau ci-contre reprend ceux qui sont nécessaires pour décoder un enregistrement de mon fichier.

En complément, à ces spécificateurs de type, le format utilisé par struct permet aussi de préciser en premier caractère l'endianness. Ici, nous sommes sur des données encodées en little-endian ce qui correspond au caractère < (inférieur à).

En synthétisant les toutes ces informations avec la structure d'un enregistrement, j'arrive à la définition de format suivantes:

endianness enregistrement
little-endian double float float float long float float float float float float float long
< d f f f l f f f f f f f l

Si je vous dis enfin que muni de ce format, il vous suffit de créer une instance de la classe struct.Struct puis d'invoquer la méthode unpack pour décoder un enregistrement, vous devriez comprendre les modifications apportées dans le code suivant:

// ...
 
try:
    s = struct.Struct("<dffflfffffffl") # Binary data format
    while True:
	record = f.read(56)
	if len(record) != 56:
	    break;
 
	print s.unpack(record) # Decode ("unpack")
 
// ...
sh$ python struct-read.py  | head -3
(38459.854166666664, 993.5999755859375, 1017.5999755859375, 0.20000000298023224, 13, 51.0, 0.0, 0.0, 24.099998474121094, 18.599998474121094, 30.0, 65.0, 0)
(38459.854861111111, 993.5999755859375, 1017.5999755859375, 0.20000000298023224, 13, 51.0, 0.0, 0.0, 24.099998474121094, 18.599998474121094, 30.0, 65.0, 0)
(38459.855555555558, 993.5999755859375, 1017.5999755859375, 3.0999999046325684, 13, 5.8000001907348633, 0.0, 0.0, 24.099998474121094, 18.599998474121094, 30.0, 65.0, 0)

Et voilà: le travail de décodage est terminé. Comme vous le voyez, la méthode unpack renvoie l'enregistrement décodé dans un tuple. Le reste du programme va être de l'exploitation classique de ce genre d'information avec Python.

Pour fignoler

Juste pour compléter le programme et en faire quelque-chose d'utile, je vais rajouter le code nécessaire pour mettre la date sous une forme plus exploitable avec Python et, disons, rechercher les extrémums de température parmi les données du fichier.

Installation

Sous Debian vous aurez besoin du paquet python-tz pour ce qui suit. Si ce logiciel n'est pas déjà sur votre machine utilisez le gestionnaire de paquets de votre distribution pour procéder à l'installation.

sh# apt-get install python-tz

Mis à part l'import du module pytz les modifications à apporter sont concentrées dans la fragment de code suivant:

origin = datetime.datetime(1899,12,30,0,0,0, tzinfo=pytz.utc)
    min = None
    max = None
 
    s = struct.Struct("<dffflfffffffl")
    while True:
        record = f.read(56)
        if len(record) != 56:
            break;
 
        fields = s.unpack(record)
        (timestamp,temp) = (fields[i] for i in (0,8))
 
        # adjust date
        timedelta = datetime.timedelta(timestamp)
        date = origin + timedelta
 
        # Record min and max
        if min is None or min[1] > temp:
            min = (date, temp)
        if max is None or max[1] < temp:
            max = (date, temp)
 
    print "min", min
    print "max", max

Comme vous le voyez, c'est du Python tout ce qu'il y a de plus ordinaire. Et rien de spécifique au fait que les données soient issues d'un fichier binaire ou d'ailleurs!

sh$ python struct-read.py
min (datetime.datetime(2005, 4, 22, 4, 30, tzinfo=<UTC>), 17.400001525878906)
max (datetime.datetime(2005, 4, 18, 16, 28, tzinfo=<UTC>), 28.5)

Écrire un fichier binaire

En présentant le module struct je disais plus haut qu'il permet non-seulement de décoder, mais aussi d'encoder des données binaires. Cela permet d'envisager non seulement de relire, mais aussi de créer ou modifier un fichier binaire.

Pour illustrer cette possibilité, imaginons que l'on ait remarqué une anomalie de fonctionnement de la station météo: à cause d'un problème de calibration de la sonde de correspondante, la température extérieure est systématiquement sous-estimée de 1,4°C. Avec les bases que nous avons vues, il est possible d'écrire un programme pour lire le fichier de données, appliquer une correction, puis enregistrer les données corrigées. Les seules nouveautés résideront dans l'utilisation de la méthode stdtypes.file.write pour écrire dans un fichier et de struct.Struct.pack pour encoder une structure en binaire. Celles-ci sont symétrique dans leur fonctionnement aux méthodes read et unpack – cela ne devrait poser aucun problème si je vous présente donc directement le code:

import struct
import datetime
 
# source file for reading (r)b
src = open("history.dat", "rb")
 
# destination file for writing (w)b
dst = open("adjusted.dat", "wb")
 
try:
    # Record struct format
    s = struct.Struct("<dffflfffffffl")
 
    while True:
	# Read a record from source file
	record = src.read(56)
	if len(record) != 56:
	    break;
 
	# Adjust data
	fields = s.unpack(record)
	adjusted = list(v+1.4 if i == 9 else v for i,v in enumerate(fields))
 
	# Encode the record and write it to the dest file
	record = s.pack(*adjusted)
	dst.write(record)
 
except IOError:
	# Your error handling here
	# Nothing for this example
	pass
finally:
    src.close()
    dst.close()

L'exécution du programme ne produit aucun affichage, mais génère un nouveau fichier adjusted.dat:

sh$ python modify-copy.py 
sh$ ls -l *.dat
-rw-r--r-- 1 sylvain sylvain 1169336 Jun  4 23:11 adjusted.dat
-rw-r--r-- 1 sylvain sylvain 1169336 May  2  2005 history.dat

Vous constaterez que l'original et la copie on la même taille – ce qui présage que tous les enregistrements ont bien été copiés. Vérifions tout de même que les données à corriger ont bien été ajustées en comparant les deux premiers enregistrements de chacun de ces fichiers:

sh$ hexdump -C history.dat -n 112
00000000  55 55 55 55 7b c7 e2 40  66 66 78 44 66 66 7e 44  |UUUU{..@ffxDff~D|
00000010  cd cc 4c 3e 0d 00 00 00  00 00 4c 42 00 00 00 00  |..L>......LB....|
00000020  00 00 00 00 cc cc c0 41  cc cc 94 41 00 00 f0 41  |.......A...A...A|
00000030  00 00 82 42 00 00 00 00  5b b0 05 5b 7b c7 e2 40  |...B....[..[{..@|
00000040  66 66 78 44 66 66 7e 44  cd cc 4c 3e 0d 00 00 00  |ffxDff~D..L>....|
00000050  00 00 4c 42 00 00 00 00  00 00 00 00 cc cc c0 41  |..LB...........A|
00000060  cc cc 94 41 00 00 f0 41  00 00 82 42 00 00 00 00  |...A...A...B....|
00000070
sh$ hexdump -C adjusted.dat -n 112
00000000  55 55 55 55 7b c7 e2 40  66 66 78 44 66 66 7e 44  |UUUU{..@ffxDff~D|
00000010  cd cc 4c 3e 0d 00 00 00  00 00 4c 42 00 00 00 00  |..L>......LB....|
00000020  00 00 00 00 cc cc c0 41  ff ff 9f 41 00 00 f0 41  |.......A...A...A|
00000030  00 00 82 42 00 00 00 00  5b b0 05 5b 7b c7 e2 40  |...B....[..[{..@|
00000040  66 66 78 44 66 66 7e 44  cd cc 4c 3e 0d 00 00 00  |ffxDff~D..L>....|
00000050  00 00 4c 42 00 00 00 00  00 00 00 00 cc cc c0 41  |..LB...........A|
00000060  ff ff 9f 41 00 00 f0 41  00 00 82 42 00 00 00 00  |...A...A...B....|
00000070

Comme on le voit, 4 octets diffèrent dans chacun des enregistrement et correspondent bien à la température ajustée de 1,4°C. Si vous ne me croyez pas sur parole, je vous laisse jouer avec unpack pour vous en convaincre.

Modifier un fichier binaire

Étape suivante de notre voyage, après avoir créé un nouveau fichier à partir d'un original laissé intact, voyons comment modifier un fichier sur place. C'est à dire en changeant directement les données dans le fichier.

À nouveau, fort peu de différence avec la version précédente. Tout au plus l'ouverture du fichier en mode r+b (lecture et modification en mode binaire). Ainsi que l'emploi de la primitive seek pour revenir en arrière dans le fichier après avoir lu un enregistrement afin de l'écraser avec sa version mise à jour:

import struct
import datetime
import os
 
# source file to read an modify (r+)b
f = open("history.dat", "r+b")
 
try:
    # Record struct format
    s = struct.Struct("<dffflfffffffl")
 
    while True:
	# Read a record from the file
	record = f.read(56)
	if len(record) != 56:
	    break;
 
	# Adjust data
	fields = s.unpack(record)
	adjusted = list(v+1.4 if i == 9 else v for i,v in enumerate(fields))
 
	# Encode the record
	record = s.pack(*adjusted)
 
	# ''Rewind'' the file one record back
	f.seek(-56,os.SEEK_CUR);
 
	# Overwrite the record with the modified version
	f.write(record)
 
except IOError:
	# Your error handling here
	# Nothing for this example
	pass
finally:
    f.close()

Les plus affutés auront remarqué qu'au moins en théorie mon programme est loin d'être efficace. En effet, je lis et j'écris chaque enregistrement en entier dans le fichier alors qu'en réalité je n'ai à modifier que 4 octets par enregistrement. Dans la pratique, agir ainsi rend le code plus lisible et maintenable que de calculer des offsets compliqués dans le fichier. D'ailleurs, entre la puissance d'une machine moderne et les différents niveaux de buffer qui entrent en jeu, je ne suis pas certain qu'il soit si facile que ça d'obtenir un gain significatif. Donc, tant qu'à faire un choix, autant prendre celui de la clarté: pour citer Donald Knuth: « premature optimization is the root of all evil ». D'autant plus que vous verrez à la section suivante qu'il y a une technique alternative qui permet d'envisager un traitement plus efficace de ce problème. Mais avant de nous y intéresser, vérifions tout de même que cette version fonctionne comme souhaité:

# Préserver le fichier de donnée original puisqu'il va être modifié
sh$ cp history.dat original.dat
sh$ chmod -w original.dat 
# Exécution du programme
sh$ python modify-in-place.py
# Vérification du résultat
sh$ diff history.dat original.dat && echo identical
Binary files history.dat and original.dat differ
sh$ diff history.dat adjusted.dat && echo identical
identical

La modification sur place a donc produit le même fichier que la modification avec copie de la section précédente.

Mappage mémoire

Les techniques précédentes utilisaient des entrée/sorties à l'ancienne qui reposent sur la gestion par le programmeur de cycles de lecture et écriture nécessaires au transfert des données par bloc du disque vers la mémoire et inversement.

Mais les systèmes d'exploitation modernes offrent une alternative plus performante sous la forme du mappage d'un fichier en mémoire (Memory-mapped file). L'idée ici est de faire correspondre un espace d'adressage à un fichier sur le disque. Le chargement et le déchargement effectif des données entre le disque et la mémoire étant dévolu au gestionnaire de mémoire virtuelle, ce qui rend ces opérations totalement transparentes au programmeur.

Ça a l'air technique – et pour tout dire ça l'est un peu. Mais cette complexité est prise en charge par le système d'exploitation. Du point de vue du programmeur, le mappage d'un fichier en mémoire se borne le plus souvent à un appel à la fonction mmap. Après cet appel, non seulement tout se passe comme si le fichier avait été intégralement chargé en RAM, mais en plus les modification apportées dans l'espace d'adressage mappé sont répercutées sur le disque. Ainsi, dans le code ci-dessous, vous verrez que j'accède au fichier mappé exactement de le même manière que si c'était un tableau en mémoire. Sans me préoccuper de savoir quand ou comment accéder aux données sur le disque:

import struct
import datetime
import mmap
 
# source file to read an modify (r+)b
f = open("history.dat", "r+b")
 
try:
    # Record struct format
    s = struct.Struct("<dffflfffffffl")
 
    # Memory map the whole file (size = 0)
    map = mmap.mmap(f.fileno(),0)
 
    # Walk record by record throught the address space
    for idx in xrange(0, map.size(), 56):
	# Unpack the current record and adjust it
	fields = s.unpack(map[idx:idx+56])
	adjusted = list(v+1.4 if i == 9 else v for i,v in enumerate(fields))
 
	# Encode the record
	map[idx:idx+56] = s.pack(*adjusted)
 
except IOError:
	# Your error handling here
	# Nothing for this example
	pass
finally:
    map.close()  # unmap
    f.close()    # close the file

Vous l'avez constaté, la différence essentielle avec les versions précedentes réside dans le fait qu'il n'y a plus d'appel explicite aux primitives de lecture/écriture read et write. La lecture et la modification des données se passe de manière transparente lors de l'accès à la variable map:

# Lecture implicite des données en mémoire
fields = s.unpack(map[idx:idx+56])
# Écriture implicite des données sur le disque
map[idx:idx+56] = s.pack(*adjusted)

Piège:

Comme souvent quand il s'agit de manipuler des fichiers, les données ne sont garanties écrite sur le disque que lorsque le fichier est fermé ou lors d'un appel explicite à la méthode mmap.flush.

mmap est réellement plus efficace?

En première approche, en réduisant le nombres de buffers intermédiaires – et donc le nombre de copies des données – mmap améliore les performances d'accès au fichier. Mais dans la pratique, le gain réel peut devenir négligeable, voire se transformer en perte selon la manière dont vous utilisez le fichier.

Le mappage mémoire est particulièrement efficace lorsque vous accédez de manière répétée en lecture écriture à certaines portions du fichier. À l'inverse, c'est une solution en principe inefficace lors de l'accès séquentiel à un fichier (comme ici!). En effet, comme le gestionnaire de mémoire paginée ne peut pas deviner que vous n'aurez plus besoin des pages chargées, il a tendance à conserver en mémoire tout le contenu déjà lu du fichier. Ce qui augmente la consommation de RAM, ce qui à son tour peut entraîner une augmentation des erreurs de page (Page fault) et donc dégrader les performances globales du système. Cette description catastrophique est cependant à relativiser dans le cas d'un petit fichier dont le chargement intégral n'aura qu'un impact marginal sur la consommation mémoire. Ici, petit s'entend comparativement à la RAM disponible. Dans le cas qui m’intéresse, je ne pense pas que le chargement d'un fichier de quelques mégaoctets sur une machine où la RAM se compte en gigaoctets soit très pénalisant. Au final, vous voyez que le choix d'utiliser mmap ou pas peut être compliqué. Dans la pratique, sauf impératifs particuliers, je vous conseille la technique qui produit le code le plus simple à comprendre et à maintenir!

Le mot de la fin

Cette exploration des techniques de manipulation de fichiers binaire en Python touche à son terme. Si nous avons fini sur la notion un peu plus avancée de mappage mémoire, pour l'essentiel il s'est agit de combiner les techniques classiques d'entrées/sorties sur des fichiers avec les fonctionnalités pack et unpack du module struct. Si j'ai présenté cette option, c'est qu'elle collait bien avec le fichier qui me servait de support. Mais il y en a d'autres: ainsi si mon fichier avait contenu des données homogènes (toutes du même type), le module array aurait sans doute été un meilleur choix. En cas de besoin, je pense qu'avec les explications donnée ici et le documentation officielle, vous devriez maintenant être en mesure d'en comprendre la fonctionnement par vous même.

Ressources