web-dev-qa-db-fra.com

Enregistrement d'un objet (persistance des données)

J'ai créé un objet comme celui-ci:

company1.name = 'banana' 
company1.value = 40

Je voudrais sauver cet objet. Comment puis je faire ça?

195
Peterstone

Vous pouvez utiliser le module pickle dans la bibliothèque standard. Voici une application élémentaire de votre exemple:

import pickle

class Company(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

with open('company_data.pkl', 'wb') as output:
    company1 = Company('banana', 40)
    pickle.dump(company1, output, pickle.HIGHEST_PROTOCOL)

    company2 = Company('spam', 42)
    pickle.dump(company2, output, pickle.HIGHEST_PROTOCOL)

del company1
del company2

with open('company_data.pkl', 'rb') as input:
    company1 = pickle.load(input)
    print(company1.name)  # -> banana
    print(company1.value)  # -> 40

    company2 = pickle.load(input)
    print(company2.name) # -> spam
    print(company2.value)  # -> 42

Vous pouvez également définir votre propre utilitaire simple, comme le suivant, qui ouvre un fichier et y écrit un seul objet:

def save_object(obj, filename):
    with open(filename, 'wb') as output:  # Overwrites any existing file.
        pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)

# sample usage
save_object(company1, 'company1.pkl')

Mise à jour:

Comme cette réponse est si populaire, j'aimerais aborder quelques sujets d'utilisation légèrement avancés.

cPickle (ou _pickle) vs pickle

Il est presque toujours préférable d'utiliser réellement le module cPickle plutôt que pickle car le premier est écrit en C et est beaucoup plus rapide. Il existe quelques différences subtiles entre eux, mais dans la plupart des situations, ils sont équivalents et la version C fournira des performances bien supérieures. Passer à cela ne pourrait pas être plus facile, il suffit de changer l'instruction import en ceci:

import cPickle as pickle

Dans Python 3, cPickle a été renommé _pickle, mais cette opération n'est plus nécessaire car le module pickle le fait désormais automatiquement — voir Quelle différence entre pickle et _pickle dans python 3? .

En résumé, vous pouvez utiliser quelque chose comme ceci pour vous assurer que votre code utilisera toujours toujours la version C lorsqu'il est disponible à la fois dans Python 2 et 3:

try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

Formats de flux de données (protocoles)

pickle peut lire et écrire des fichiers dans plusieurs formats différents, spécifiques à Python, appelés protocoles comme décrit dans la documentation , "Protocole version 0" est ASCII et donc "lisible par l'homme". Les versions> 1 sont binaires et la version la plus élevée disponible dépend de la version de Python utilisée. La valeur par défaut dépend également de Python version. Dans Python 2, la version par défaut du protocole était 0, mais dans Python 3.7, il s'agissait de la version du protocole 3. Dans Python 3.x, le module avait un pickle.DEFAULT_PROTOCOL ajouté, mais cela n'existe pas dans Python 2.

Heureusement, il y a un raccourci pour écrire pickle.HIGHEST_PROTOCOL dans chaque appel (en supposant que vous le fassiez et ce que vous faites habituellement), utilisez simplement le nombre littéral -1 - similaire au référencement du dernier élément d'une séquence via un index négatif . Donc, au lieu d'écrire:

pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)

Vous pouvez simplement écrire:

pickle.dump(obj, output, -1)

De toute façon, vous n’auriez spécifié le protocole qu’une fois si vous avez créé un objet Pickler à utiliser dans plusieurs opérations de pickle:

pickler = pickle.Pickler(output, -1)
pickler.dump(obj1)
pickler.dump(obj2)
   etc...

Remarque : Si vous êtes dans un environnement exécutant différentes versions de Python, vous voudrez probablement utiliser explicitement (par exemple, un code fixe) un numéro de protocole spécifique que tous peuvent lire. (les versions ultérieures peuvent généralement lire les fichiers produits par les précédentes).

Objets multiples

Bien qu'un fichier de cornichon puisse contenir un nombre quelconque d'objets décapés, comme le montrent les exemples ci-dessus, lorsqu'il y en a un nombre inconnu, il est souvent plus facile de les stocker tous dans une sorte de fichier. conteneur de taille variable, comme un list, Tuple ou dict et écrivez-les tous dans le fichier en un seul appel:

tech_companies = [
    Company('Apple', 114.18), Company('Google', 908.60), Company('Microsoft', 69.18)
]
save_object(tech_companies, 'tech_companies.pkl')

et restaurez la liste et tout ce qu'elle contient plus tard avec:

with open('tech_companies.pkl', 'rb') as input:
    tech_companies = pickle.load(input)

L’avantage majeur est que vous n’avez pas besoin de savoir combien d’instances d’objet sont sauvegardées pour pouvoir les recharger plus tard (bien que le faire sans cette information est possible, il en faut légèrement code spécialisé). Voir les réponses à la question connexe Enregistrement et chargement de plusieurs objets dans un fichier pickle? pour plus de détails sur les différentes façons de le faire. Personnellement I comme @Lutz Prechelt réponse le meilleur. Voici le adapté aux exemples ici:

class Company:
    def __init__(self, name, value):
        self.name = name
        self.value = value

def pickled_items(filename):
    """ Unpickle a file of pickled data. """
    with open(filename, "rb") as f:
        while True:
            try:
                yield pickle.load(f)
            except EOFError:
                break

print('Companies in pickle file:')
for company in pickled_items('company_data.pkl'):
    print('  name: {}, value: {}'.format(company.name, company.value))
387
martineau

Je pense que c'est une hypothèse assez forte de supposer que l'objet est un class. Et si ce n'est pas un class? Il y a aussi l'hypothèse que l'objet n'a pas été défini dans l'interpréteur. Et si c'était défini dans l'interprète? Et si les attributs étaient ajoutés dynamiquement? Lorsque certains objets python ont des attributs ajoutés à leur __dict__ après leur création, pickle ne respecte pas l'ajout de ces attributs (c.-à-d. Qu'il "oublie" qu'ils ont été ajoutés - parce que pickle se sérialise par référence à la définition de l'objet).

Dans tous ces cas, pickle et cPickle peuvent vous échouer horriblement.

Si vous souhaitez enregistrer une object (créée arbitrairement), dans laquelle vous avez des attributs (soit ajoutés à la définition de l'objet, soit ultérieurement)… votre meilleur choix est d'utiliser dill, qui peut sérialiser à peu près tout en python.

Nous commençons avec un cours…

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> with open('company.pkl', 'wb') as f:
...     pickle.dump(company1, f, pickle.HIGHEST_PROTOCOL)
... 
>>> 

Maintenant, arrêtez et redémarrez ...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> with open('company.pkl', 'rb') as f:
...     company1 = pickle.load(f)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1378, in load
    return Unpickler(file).load()
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 858, in load
dispatch[key](self)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1090, in load_global
    klass = self.find_class(module, name)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1126, in find_class
    klass = getattr(mod, name)
AttributeError: 'module' object has no attribute 'Company'
>>> 

Oups… pickle ne peut pas le gérer. Essayons dill. Nous allons jeter dans un autre type d'objet (un lambda) pour une bonne mesure.

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill       
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> 
>>> company2 = lambda x:x
>>> company2.name = 'rhubarb'
>>> company2.value = 42
>>> 
>>> with open('company_dill.pkl', 'wb') as f:
...     dill.dump(company1, f)
...     dill.dump(company2, f)
... 
>>> 

Et maintenant, lisez le fichier.

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> with open('company_dill.pkl', 'rb') as f:
...     company1 = dill.load(f)
...     company2 = dill.load(f)
... 
>>> company1 
<__main__.Company instance at 0x107909128>
>>> company1.name
'banana'
>>> company1.value
40
>>> company2.name
'rhubarb'
>>> company2.value
42
>>>    

Ça marche. La raison pour laquelle pickle échoue et que dill ne l’est pas, c’est que dill traite __main__ comme un module (pour la plupart) et peut également conserver des définitions de classe au lieu de décapage par référence (comme pickle le fait). La raison pour laquelle dill peut mariner un lambda est qu'il lui donne un nom… alors la magie du décapage peut se produire.

En fait, il existe un moyen plus simple de sauvegarder tous ces objets, surtout si vous en avez créé beaucoup. Il suffit de vider toute la session python et d'y revenir plus tard.

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> 
>>> company2 = lambda x:x
>>> company2.name = 'rhubarb'
>>> company2.value = 42
>>> 
>>> dill.dump_session('dill.pkl')
>>> 

Maintenant, éteignez votre ordinateur, dégustez un expresso ou quoi que ce soit, et revenez plus tard ...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> dill.load_session('dill.pkl')
>>> company1.name
'banana'
>>> company1.value
40
>>> company2.name
'rhubarb'
>>> company2.value
42
>>> company2
<function <lambda> at 0x1065f2938>

Le seul inconvénient majeur est que dill ne fait pas partie de la bibliothèque standard python. Donc, si vous ne pouvez pas installer un package python sur votre serveur, vous ne pouvez pas l'utiliser.

Toutefois, si vous pouvez installer les packages python sur votre système, vous pouvez obtenir le dernier fichier dill avec git+https://github.com/uqfoundation/dill.git@master#Egg=dill. Et vous pouvez obtenir la dernière version publiée avec pip install dill.

44
Mike McKerns

Vous pouvez utiliser anycache pour faire le travail à votre place. Il considère tous les détails:

  • Il utilise dill comme backend, qui étend le module python pickle pour gérer lambda et toutes les fonctionnalités de Nice python.
  • Il stocke différents objets dans différents fichiers et les recharge correctement.
  • Limite la taille du cache
  • Permet l'effacement du cache
  • Permet le partage d'objets entre plusieurs exécutions
  • Permet le respect des fichiers d'entrée qui influencent le résultat

En supposant que vous ayez une fonction myfunc qui crée l’instance:

from anycache import anycache

class Company(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

@anycache(cachedir='/path/to/your/cache')    
def myfunc(name, value)
    return Company(name, value)

Anycache appelle myfunc pour la première fois et transforme le résultat en fichier dans cachedir en utilisant un identifiant unique (en fonction du nom de la fonction et de ses arguments) en tant que nom de fichier. Lors de toute exécution consécutive, l’objet décapé est chargé. Si la cachedir est préservée entre les exécutions de python, l'objet décapé est extrait de la précédente exécution de python.

Pour plus de détails, voir le documentation

3
c0fec0de