Intéressé par des cours d'informatique en ligne ?
Visitez mon nouveau 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.
- explorer en ligne ces fichiers;
- télécharger une archive ZIP de ce projet;
- ou cloner ce projet si vous avez un client Git sur votre machine:
git clone git://github.com/s-leroux/HEAVYWEATHER-WS3600
Sommaire
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.
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é | |
---|---|---|---|---|
00 | double | [8] | Date | Jour fractionnaire depuis le 30 dec 1899 00:00:00 (GMT) |
08 | float | [4] | Pression atmosphérique (abs) | millibars |
12 | float | [4] | Pression atmosphérique (rel) | millibars |
16 | float | [4] | Vitesse du vent | m/s |
20 | long | [4] | Direction du vent | 0-15 gradué à partir de Nord (0) dans le sens horaire: N, NNE, NE, ENE, … |
24 | float | [4] | Vitesse du vent (rafale) | m/s |
28 | float | [4] | Précipitations (totales) | millimètres |
32 | float | [4] | Précipitations (nouvelles) | millimètres |
36 | float | [4] | Température sous abris | °Celsius |
40 | float | [4] | Température extérieure | °Celsius |
44 | float | [4] | Humidité sous abris | % |
48 | float | [4] | Humidité extérieure | % |
52 | long (?) | [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:
- j'importe le module struct – il ne nous servira que dans la section suivante, mais au moins je ne l'oublierai pas;
- je lis le fichier par paquet de 56 octet – soit un enregistrement à la fois;
- j’interromps la boucle de lecture dès que que je ne peux plus lire un enregistrement complet.
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 |
---|---|---|
d | double | IEEE 754 binary64 |
f | float | IEEE 754 binary32 |
l | long | 32 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
- (en) HeavyWeather History File Format – compiled by Ben Udall
- (fr) Format des fichiers générés par Heayvy Weather – par pschnell sur www.station-meteo.com
- (fr) Heavyweather.info – site du fabriquant de la station météo WS3600
- (en) Python - parsing binary data files – Réalisation pas à pas d'un module Python pour décoder un format de fichier binaire propriétaire par Kurt Schwehr