Python, shelve. Persistance des données.

In [8]:
from termcolor import cprint
from dateutil.tz import gettz
import datetime as dt
import locale

locale.setlocale(locale.LC_ALL, ('fr_FR', 'UTF-8'))

tzi = gettz('Europe/Brussels')
cprint('{} {}'.format('danielhagnoul', dt.datetime.now(tz=tzi).isoformat()), 'red')
danielhagnoul 2020-05-21T19:03:47.470679+02:00

Lorsqu'un programme a besoin de sauvegarder son état lorsqu'il s'arrête et de récupérer son état lorsqu'il démarre, on peut utiliser le module shelve qui utilise en interne le module pickle pour transformer un objet python complexe en un flux d'octets et inversement.

Le module shelve crée des fichiers qui gèrent un dictionnaire de données. La sauvegarde et la restauration de données sont triviales. Les complications éventuelles dépendent de votre code, de la quantité de données à rendre persistante et du degré d'intrication entre ces données.

Prenons un exemple simple, l'ébauche d'une classe Client et d'une classe Banque.

On doit pouvoir créer un nouveau client (compte de banque) et gérer les opérations de débit et de crédit. L'ébauche de la classe Banque à un dictionnaire clients qui contient l'état des comptes à l'instant T. Ce dictionnaire doit être restauré à l'ouverture du programme et sauvegardé avant la fin du programme.

La classe Client donne un ID unique à chaque client, pour cela il utilise la méthode count du module itertools. L'ID augmente de 1 à chaque création de clients. Mais il faut restaurer et sauvegarder son état à chaque fois que la Banque travaille sinon count reprend du début et génère des IDs qui existe déjà.

class Client

In [9]:
from itertools import count

class Client:
    _ids = count(1)

    def __init__(self, nom: str = '', prenom: str = ''):
        self.__id = next(self._ids)
        self.__nom = nom
        self.__prenom = prenom
        self.__solde = 0

    @property
    def id(self):
        return self.__id

    @property
    def nom(self):
        return self.__nom

    @property
    def prenom(self):
        return self.__prenom

    @property
    def solde(self):
        return self.__solde

    def __repr__(self):
        s = "< Client : id = {0.id} nom = {0.nom}, prénom = {0.prenom}, ".format(
            self)
        s += locale.currency(self.solde, symbol=True, grouping=True) + ">"
        return s

    __str__ = __repr__

    def credit(self, montant: float = 0):
        if montant > 0:
            self.__solde += montant

    def debit(self, montant: float = 0):
        if montant > 0 and self.__solde > montant:
            self.__solde -= montant

class Banque

In [10]:
import shelve

# Le chemin du fichier dépend de votre environnement de travail
shelve_file_name = 'F:/test-python/Tests/blog/shelve_clients'

class Banque:
    def __init__(self):
        self.dict_clients = self.restore_clients()

    def save_clients(self):
        print("{:-^30s}".format("Fermeture de la banque"))
        self.liste_clients()
        with shelve.open(shelve_file_name, writeback=True) as dico:
            dico['clients'] = self.dict_clients
            dico['ids'] = Client._ids

    def restore_clients(self):
        print("{:-^30s}".format("Ouverture de la banque"))
        dict_clients = {}
        with shelve.open(shelve_file_name) as dico:
            if 'clients' in dico:
                dict_clients = dico['clients']
            if 'ids' in dico:
                Client._ids = dico['ids']
        if len(dict_clients) == 0:
            nom = "Hagnoul"
            prenom = "Daniel"
            obj = Client(nom, prenom)
            obj.credit(10000.0)
            dict_clients[obj.id] = obj
        return dict_clients

    def add_client(self, nom, prenom, solde):
        obj = Client(nom, prenom)
        obj.credit(solde)
        if obj.id not in self.dict_clients:
            self.dict_clients[obj.id] = obj
        else:
            raise ValueError("Erreur fatale, cette valeur d'id existe déjà")

    def liste_clients(self):
        for obj in self.dict_clients.values():
            print(obj)

Utilisations de la banque

In [11]:
b = Banque()
b.liste_clients()
b.add_client("Lapoire", "Albertine", 7000)
b.dict_clients[1].debit(27.69)
b.save_clients()
----Ouverture de la banque----
< Client : id = 1 nom = Hagnoul, prénom = Daniel, 10 000,00 €>
----Fermeture de la banque----
< Client : id = 1 nom = Hagnoul, prénom = Daniel, 9 972,31 €>
< Client : id = 2 nom = Lapoire, prénom = Albertine, 7 000,00 €>
In [12]:
b = Banque()
b.liste_clients()
b.add_client("Malautrou", "Pierre", 278.27)
b.dict_clients[2].debit(312.59)
b.save_clients()
----Ouverture de la banque----
< Client : id = 1 nom = Hagnoul, prénom = Daniel, 9 972,31 €>
< Client : id = 2 nom = Lapoire, prénom = Albertine, 7 000,00 €>
----Fermeture de la banque----
< Client : id = 1 nom = Hagnoul, prénom = Daniel, 9 972,31 €>
< Client : id = 2 nom = Lapoire, prénom = Albertine, 6 687,41 €>
< Client : id = 3 nom = Malautrou, prénom = Pierre, 278,27 €>
In [13]:
b = Banque()
b.liste_clients()
b.dict_clients[1].credit(2157.00)
b.save_clients()
----Ouverture de la banque----
< Client : id = 1 nom = Hagnoul, prénom = Daniel, 9 972,31 €>
< Client : id = 2 nom = Lapoire, prénom = Albertine, 6 687,41 €>
< Client : id = 3 nom = Malautrou, prénom = Pierre, 278,27 €>
----Fermeture de la banque----
< Client : id = 1 nom = Hagnoul, prénom = Daniel, 12 129,31 €>
< Client : id = 2 nom = Lapoire, prénom = Albertine, 6 687,41 €>
< Client : id = 3 nom = Malautrou, prénom = Pierre, 278,27 €>
In [14]:
!jupyter nbconvert --to html shelve_banque.ipynb
[NbConvertApp] Converting notebook shelve_banque.ipynb to html
[NbConvertApp] Writing 291348 bytes to shelve_banque.html