Chapitre 14/3 : les fichiers – la sérialisation des données avec le module pickle

Dans ce chapitre, nous allons découvrir un module très intéressant puisqu’il permet d’enregistrer des données dans un fichier binaire et de les restituer ultérieurement avec leur type initial.

En effet, jusqu’ici, dans les deux chapitres précédents, nous n’avons enregistré que des données de type string dans les fichiers que nous avons créés. Si nous voulions par exemple enregistrer le nombre entier 9, il fallait d’abord convertir ce dernier en chaîne de caractères (ligne n° 3) et effectuer ensuite l’opération inverse pour pouvoir l’additionner avec un autre nombre entier (ligne n° 6).

number = 9
with open("mon_fichier", 'w') as file_:
    file_.write(str(number))
with open("mon_fichier", 'r') as file_:
    number = file_.readline()
    result = 4 + int(number)
    print("4 + {} = {}".format(number, result))

Le module pickle

Mais ça, c’était avant!… Avant de découvrir le module pickle qui va nous permettre d’enregistrer dans un fichier binaire, des nombres entiers (type int), des nombres décimaux (type float), des listes (type list), des dictionnaires (type dict) et même des objets que l’on a soi-même instanciés! Ce procédé, d’une grande utilitude, porte un nom : la sérialisation des données. Voici comment cela fonctionne. J’ai commenté chaque ligne de ce code (un peu plus bas):

import pickle

int_number = 23
float_number = 15.89
first_names = ["Pamphile", "Théophane", "Esclarmonde"]
dictionary = {"Pamphile" : 32, "Théophane" : 54, "Esclarmonde" : 45}

class First_name(object):
    def __init__(self, first_names):
        self.p = first_names[0]
        self.t = first_names[1]
        self.e = first_names[2]
    def majuscules(self):
        return "{}, {} et {}".format(self.p.upper(), self.t.upper(), self.e.upper())

upper_names = First_name(first_names)

with  open("donnees_enregistrees", 'wb') as file_:
    pickle.dump(int_number, file_)
    pickle.dump(float_number, file_)
    pickle.dump(first_names, file_)
    pickle.dump(dictionary, file_)
    pickle.dump(upper_names, file_)

with  open("donnees_enregistrees", 'rb') as file_:
    i = pickle.load(file_)
    print(i, type(i))
    j = pickle.load(file_)
    print(j, type(j))
    k = pickle.load(file_)
    print(k, type(k))
    l = pickle.load(file_)
    print(l, type(l))
    m = pickle.load(file_)
    print(m.majuscules(), type(m))
    n = pickle.load(file_)
    print(n, type(n))

Exécution du code :

23 <class ‘int’>
15.89 <class ‘float’>
[‘Pamphile’, ‘Théophane’, ‘Esclarmonde’] <class ‘list’>
{‘Théophane’: 54, ‘Pamphile’: 32, ‘Esclarmonde’: 45} <class ‘dict’>
PAMPHILE, THÉOPHANE et ESCLARMONDE <class ‘__main__.First_name’>
Traceback (most recent call last):
File « /home/benoit/test_pickle.py », line 35, in
n = pickle.load(file_)
EOFError: Ran out of input

Enregistrement des données

Ligne n° 1 : Importation du module pickle

Lignes n° 3 à 6 : création de quatre objets de type différents (int, float, list et dict)

Lignes n° 8 à 14 : Définition d’une classe First_name avec la méthode majuscules() qui permet de mettre en majuscules, une liste de prénoms passée en argument

Ligne n° 16 : Création de l’objet upper_names par instanciation de la classe First_name.

Ligne n° 18 : Création et ouverture d’un fichier en mode ‘wb’ c’est-à-dire en mode écriture et binaire. Le fichier s’appelle « donnees_enregistrees »

Lignes n° 19 à 23 : Enregistrement dans le fichier, de données de cinq types différents, à savoir int, float, list, dict et objet personnel. Cet enregistrement s’effectue grâce à la fonction dump() du module pickle. Cette fonction prend deux arguments qui sont la variable à enregistrer et l’objet correspondant au fichier cible (L’objet file_ correspond au fichier donnees_enregistrees)

À ce stade, vous pouvez vous amuser à ouvrir le fichier binaire « donnees_enregistrees », vous obtiendrez un incompréhensible baragouin où surnagent quelques prénoms qui n’ont pas encore sombré dans les abysses de la conscience-machine.

donnees_enregistrees

Restitution des données

Ligne n° 25 : Ouverture du fichier « donnees_enregistrees » en mode ‘rb’ c’est-à-dire en mode lecture et binaire. C’est la fonction load() du module pickle qui va nous permettre de restituer chaque donnée en respectant leur type d’origine! La fonction load() prend un argument qui est l’objet (file_) correspondant au fichier « donnees_enregistrees ».

Ligne n° 26 : restitution de l’objet correspondant au nombre entier 23 que nous stockons dans la variable i.

Ligne n° 27 : Le print de i et de son type nous confirme qu’il s’agit bien du nombre entier 23.

Lignes n° 28 à 33 : Même opération avec les variables j, k et l qui récupèrent respectivement un objet de type float (15.89), une liste et un dictionnaire.

Ligne n°34 : Ici, c’est un peu particulier… La variable m stocke l’objet que nous avons nous-mêmes créé par instanciation de la classe First_name.

Ligne n° 35 : Nous faisons un print() de m.majuscules(), c’est-à-dire que j’applique la méthode majuscules(), définie dans la classe First_name, sur l’objet m. Le résultat qui s’affiche est PAMPHILE, THÉOPHANE et ESCLARMONDE.

Ligne n° 36 : Toutes les données ont été restituées si bien que la dernière variable (n) n’a plus rien à stocker. C’est la raison pour laquelle Python nous retourne une exception (EOFError: Ran out of input).

Découper une base de données en plusieurs fichiers portant l’extension .pkl

Nous pouvons bien évidemment modifier les valeurs extraites et les enregistrer de nouveau à l’aide de la fonction dump(). Cela dit, si la base de données est très importante, le processus risque d’être un peu lourd et par conséquent, il s’en trouvera fortement ralenti.

Une solution consiste à découper la base de données en plusieurs fichiers portant l’extension ‘.pkl’. Analysons le code ci-dessous:

import pickle
import glob

int_number = 23
float_number = 15.89
first_names = ["Pamphile", "Théophane", "Esclarmonde"]
dictionary = {"Pamphile" : 32, "Théophane" : 54, "Esclarmonde" : 45}

class First_name(object):
    def __init__(self, first_names):
        self.p = first_names[0]
        self.t = first_names[1]
        self.e = first_names[2]
    def majuscules(self):
        return "{}, {} et {}".format(self.p.upper(), self.t.upper(), self.e.upper())

upper_names = First_name(first_names)

objects_list = [("int_number", int_number),
                ("float_number", float_number),
                ("first_names", first_names),
                ("dictionary", dictionary),
                ("upper_names", upper_names)]

for (x, y)in objects_list:
    with open(x + '.pkl', 'wb')as file_:
        pickle.dump(y, file_)

for f in glob.glob('*.pkl'):
    with open(f, 'rb') as file_:
        data = pickle.load(file_)
        if type(data) == dict:
            print(data)
            data['Théophane'] = 89
            print(data)
with open(f, 'wb') as file_:
pickle.dump(data, file_)

Exécution du code :

{‘Théophane’: 54, ‘Pamphile’: 32, ‘Esclarmonde’: 45}
{‘Théophane’: 89, ‘Pamphile’: 32, ‘Esclarmonde’: 45}

Ligne n° 2 : importation du module glob qui va nous permettre de faire l’inventaire du répertoire courant (/home/benoit) en lui appliquant un filtre (en l’occurrence l’extension *.pkl)

Ligne n° 19 : Nous créons une liste de tuples contenant les cinq objets de type différent que nous avons précédemment instanciés, associés au nom de leur futur fichier .pkl .

Lignes n° 25 à 27 : À l’aide d’une boucle for, nous créons le chemin de chaque fichier (x + ‘.pkl’) et nous enregistrons les données qui lui correspondent (y).

Ligne n° 29 : Nous faisons appel au module glob pour lister les fichiers portant l’extension .pkl.

Ligne n° 31 : Extraction des données

Ligne n° 32 : Nous créons une condition (Si les données sont de type dictionnaire…).

Ligne n° 34 : Nous modifions l’âge de Théophane.

Lignes n° 35 et 36 : Nous enregistrons les données modifiées dans le fichier binaire.

Au bout du compte, nous n’avons déchargé et chargé que les données que nous souhaitions modifier.