UKOnline

Fichier binaire

Maintenant que l'on sait manipuler des fichiers textes, intéressons-nous à la manipulation de bas niveau, à savoir aux fichiers binaires. L'avantage par rapport aux fichiers textes est qu'ils sont plus compacts en terme d'espace occupé et également plus rapides à lire et écrire. Par contre, la difficulté avec ces fichiers est qu'il faut, pour pouvoir les manipuler, connaitre précisément l'organisation des données en leur sein.

L'ouverture et la fermeture d'un fichier binaire se fait de la même manière qu'avec un fichier texte, si ce n'est qu'il faut spécifier le bon mode. Il suffit simplement d'ajouter b dans le descripteur de mode, pour signaler qu'il s'agit d'un fichier binaire. Pour la lecture, on utilise donc rb et l'écriture se définit avec wb. Pour ouvrir le fichier binaire data.bin en lecture, il suffit d'écrire :

Lecture et écriture d'objets avec pickle

Le module pickle propose plusieurs fonctions qui vont permettre de directement lire et écrire des objets Python au format binaire depuis un fichier, sans que l'on ait besoin de savoir comment ils sont convertis en binaire. L'avantage de cette façon de procéder réside donc dans sa simplicité d'utilisation.

Écriture

Commençons par voir comment écrire des données dans un fichier binaire avec le module pickle. L'exemple suivant écrit une chaine de caractères suivie d'une liste de cinq nombre entiers dans le fichier data.bin, en mode binaire :

Après avoir importé le module pickle, on ouvre le fichier data.bin en mode binaire et en écriture. On utilise ensuite la fonction dump du module pickle pour écrire un objet dans le fichier. Cette fonction nécessite trois paramètres : une référence vers l'objet à écrire, une référence vers le fichier où écrire et enfin une option. Cette dernière définit notamment la manière avec laquelle les objets sont traduits en binaire et est souvent simplement fixée à pickle.HIGHEST_PROTOCOL.

Comme on peut le voir sur la figure 5, le fichier généré par l'exécution de ce programme est bel et bien un fichier binaire, pas lisible directement avec un éditeur texte.

Fichier binaire
Un fichier binaire ne sait pas être lu directement avec un éditeur de texte.

L'écriture d'un objet dans un fichier binaire peut générer une erreur de type pickle.PicklingError si jamais l'objet ne peut pas être converti. De plus, l'exception de type IOError est toujours possible en cas d'erreur d'écriture dans le fichier.

Lecture

Pour lire un fichier binaire, on utilise simplement la fonction load du module pickle. Il faut lire les objets successivement dans le fichier, avec autant d'appels que nécessaires à la fonction load. Les instructions suivantes lisent le fichier qui a été écrit par l'exemple précédent :

La fonction load prend en seul paramètre une référence vers le fichier dans lequel elle doit lire un objet. Cette lecture peut provoquer une erreur de type pickle.UnpicklingError si jamais l'objet ne sait pas être lu depuis le fichier. De nouveau, une exception de type IOError peut se produire, en cas d'erreur de lecture cette fois-ci.

L'exécution du programme récupère bien les deux objets qui étaient stockés dans le fichier data.bin, comme en témoigne le résultat affiché :

Temperature (2016)
[12, -9, 7, 112, 99]

Lecture et écriture de données primitives avec struct

Le module struct permet également de lire et écrire dans un fichier binaire, mais à un niveau plus bas. Il offre en effet des fonctions permettant de lire et écrire des données primitives ; on effectue avec ce dernier des manipulations brutes de données binaires.

Encodage/décodage de chaine de caractères

Comme on l'a vu précédemment, un caractère n'est rien d'autre qu'un nombre entier, à savoir son identifiant numérique. On peut convertir une chaine de caractères en la suite d'octets correspondants, à l'aide de la méthode encode, qui prend en paramètre l'encodage désiré. On obtient en retour un objet bytes, qui représente une liste d'octets.

L'exemple suivant encode une chaine de caractères en UTF-8 et affiche la liste des différents octets correspondants :

<class 'bytes'>
b'Hello'
72
101
108
108
111

Comme on peut le constater sur le résultat de l'exécution, la variable data est bien de type bytes. De plus, si on affiche simplement la variable, Python affiche la chaine de caractères correspondant, préfixée d'un b pour se rappeler que c'est une séquence d'octets et pas de caractères. Enfin, la boucle for qui parcourt data affiche bien cinq nombre entiers, qui sont les identifiants numériques des lettres du mot « Hello ».

Dans l'autre sens, on peut convertir une séquence d'octets représentée par un objet bytes en une chaine de caractères avec la méthode decode. Pour reconvertir la variable data en une chaine de caractères, on écrit l'instruction suivante :

L'exécution de cette instruction affiche bien Hello, sans le préfixe b cette fois-ci puisqu'il s'agit d'une chaine de caractères de type str :

Hello

Écriture

Reprenons le même exemple que précédemment, à savoir l'écriture d'une chaine de caractères et d'une liste d'entiers. Pour utiliser le module struct, il faut avant tout définir précisément le format de la donnée à écrire, octet par octet. La figure 6 montre comment on va stocker ces données dans le fichier :

  1. on commence par écrire un nombre entier, qui représente le nombre de caractères que contient la chaine à écrire ;
  2. viennent ensuite les caractères de la chaine en question ;
  3. puis on écrit de nouveau un nombre entier, qui représente cette fois-ci le nombre d'éléments de la liste d'entiers ;
  4. et on termine avec les éléments de la liste.
Format de fichier binaire
Pour stocker des données au format binaire dans un fichier à l'aide du module struct, il faut définir précisément le format avec lequel ce sera réalisé.

Pour écrire une séquence d'octets dans un fichier binaire, on utilise la fonction pack du module struct. Cette dernière prend en paramètres une chaine de caractères qui décrit le type de la donnée à écrire et la donnée en question. Le tableau de la figure 7 reprend les principaux descripteurs de type de donnée à utiliser avec la fonction pack.

Caractère Description
h Nombre entier signé (court)
H Nombre entier non signé (court)
i Nombre entier signé
I Nombre entier non signé
f Nombre flottant
c Caractère
s Chaine de caractères
? Booléen
Pour écrire des données dans un fichier binaire avec la fonction pack du module struct, il faut décrire le type de la donnée avec une chaine de caractères.

Pour écrire dans le fichier binaire, on passe par la méthode write, comme avec les fichiers texte, à qui on passe en paramètre le résultat de l'appel de la fonction pack. Voici comment l'exemple précédent, qui écrit une chaine de caractères suivie d'une liste de nombre entiers, se réécrit avec le module struct :

On écrit donc le nombre de caractères de la chaine comme un entier (H) puis, grâce à une boucle, chacun des caractères (c) encodé avec UTF-8. On suit exactement le même principe pour la liste d'entiers.

On aurait pu écrire les données d'une autre manière, en passant directement à pack tous les caractères ou tous les nombres entiers en une fois. Il suffit pour cela de faire précéder le descripteur de type de donnée par le nombre d'éléments à écrire.

On peut donc modifier l'exemple précédent en remplaçant les instructions qui font l'écriture avec les suivantes :

Lecture

La lecture d'un fichier binaire créé avec struct se fait avec la fonction unpack qui prend deux paramètres, à savoir le format décrivant le type de donnée à lire et la séquence d'octets lue à l'aide de la fonction read sur le fichier. La fonction unpack renvoie un tuple avec les données qu'elle a lues, ce qui fait que lorsqu'on ne souhaite lire qu'un seul élément, il va falloir récupérer le premier élément du tuple comme on peut le voir sur l'exemple suivant qui lit le fichier créé précédemment :

Lorsqu'on effectue la lecture dans le fichier, avec la méthode read, il faut préciser le nombre d'octets que l'on veut lire. Un nombre entier non signé court occupe deux octets et un caractère en occupe un.

Notez enfin que ce programme de lecture fonctionne évidemment avec les deux manières d'écrire que l'on vient de voir. En effet, les deux façons de procéder écrivent les données dans le même ordre dans le fichier binaire. Il est donc très important de bien définir le format dans lequel les données sont écrites, ce qui permet de lire et écrire des fichiers écrits avec le module struct.

Tout comme pour l'écriture, on peut simplifier la lecture en lisant directement plusieurs données du même type :

Le module struct est plus complexe à utiliser que le module pickle, ce dernier écrivant directement et automatiquement des objets Python dans un fichier binaire, alors qu'il faut soi-même définir le format lorsqu'on utilise le module struct. Par contre, l'avantage du module struct est qu'il génère un fichier binaire bien plus compact. Pour l'exemple que l'on a utilisé, on se retrouve avec un fichier qui fait $33$~octets au lieu de $63$ pour le fichier créé avec le module pickle.