PyMOTW : pickle and cPickle
serialisation d'objet Python
Objectif : sérialisation d'objet Python
Version : 1.4 (pickle), 1.5 (cPickle)
Le module pickle implémente un algorithme capable de transformer n'importe quel objet Python en suites d'octets (c'est ce qu'on appelle la "sérialisation"). Ce flux d'octets peut être transmis ou sauvegardé afin de pouvoir reconstruire un objet identique (c'est a dire ayant les mêmes valeurs d'attributs).
Le module cPickle est l'implémentation en C du même algorithme. Il est beaucoup plus rapide que l'implémentation en Python utilisé par pickle. La contrepartie est qu'il n'autorise pas l'héritage de la classe Pickle par vos propres classes. Si la possibilité d'hériter de Pickle n'est pas importante pour vous, vous préférerez probablement utiliser cPickle.
NdT: Pour les versions de Python postérieure à la 3.0, cPickle et pickle ont été fusionnés. En effet, l'implémentation en C de pickle est automatiquement utilisé chaque fois qu'elle est disponible, et ce de manière transparente. Il suffit donc d'importer pickle.
Avertissement
La documentation du module pickle indique clairement que le
module ne garantit en aucune façon la sécurité et l'intégrité des données.
L'utilisation de pickle pour de la communication inter-processus ou du
stockage de données doit donc se faire avec la plus grande prudence. De telles
données ne doivent en aucun cas être considérées comme fiables.
Importation
Une pratique courante est d'essayer en premier lieu d'importer cPickle et de lui attribuer l'alias pickle. Si l'importation échoue, on se rabat sur l'implémentation en Python, pickle. Ceci vous permettra d'accéder à l'implémentation la plus rapide si elle est disponible et sinon d'utiliser l'implémentation portable.
try: import cPickle as pickle except: import pickle
NdT: comme nous l'avons déjà dit, pour les versions de Python postérieure à la 3.0, il n'y a plus besoin de faire cette distinction, le code suivant est strictement équivalent :
import pickle
Coder et décoder des données en chaînes de caractères
L'exemple qui suit code, à l'aide de pickle une structure de
données sous forme de chaîne puis affiche cette chaîne. Bien que la structure
de données soit ici définie entièrement à l'aide des type nativement supportés
de Python, les instances de n'importe quelle classe peuvent être "picklées",
ainsi que nous le montrerons un peu plus tard. C'est dans un soucis de
simplicité que seuls des types de bases de Python sont utilisés ici.
Utilisons maintenant pickle.dump() pour créer une
représentation de nos données en chaîne de caractères.
try:
import cPickle as pickle
except:
import pickle
import pprint
data = [ { 'a':'A', 'b':2, 'c':3.0 } ]
print 'DATA:',
pprint.pprint(data)
data_string = pickle.dumps(data)
print 'PICKLE:', data_string
Par défaut, pickle n'utilise que des caractères ASCII. Un format binaire plus compact est également disponible mais nous nous contenterons de l'ASCII pour ces exemples.
Une fois que les données sont sérialisées, vous pouvez les écrire dans un fichier, un socket, un flux, un pipe, etc. Par la suite il est possible de les lire et de dé-sérialiser les données afin de construire un nouvel objet munit des même valeurs.
try:
import cPickle as pickle
except:
import pickle
import pprint
data1 = [ { 'a':'A', 'b':2, 'c':3.0 } ]
print 'BEFORE:',
pprint.pprint(data1)
data1_string = pickle.dumps(data1)
data2 = pickle.loads(data1_string)
print 'AFTER:',
pprint.pprint(data2)
print 'SAME?:', (data1 is data2)
print 'EQUAL?:', (data1 == data2)
Comme vous pouvez le constater et ainsi que nous nous y attentions, l'objet créé est égal à l'objet original mais que ce n'est pas le même :
BEFORE:[{'a': 'A', 'b': 2, 'c': 3.0}]
AFTER:[{'a': 'A', 'b': 2, 'c': 3.0}]
SAME?: False
EQUAL?: True
Interaction avec les flux
En plus de dumps() et loads(), pickle propose d'autres fonctions utiles pour travailler avec les flux semblables aux fichiers. Ainsi il est possible d'écrire plusieurs objets dans un flux, et ensuite de les lire depuis le flux, le tout sans savoir à l'avance combien il y en a et de quelle taille ils seront.
try:
import cPickle as pickle
except:
import pickle
import pprint
from StringIO import StringIO
class SimpleObject(object):
def __init__(self, name):
self.name = name
l = list(name)
l.reverse()
self.name_backwards = ''.join(l)
return
data = []
data.append(SimpleObject('pickle'))
data.append(SimpleObject('cPickle'))
data.append(SimpleObject('last'))
# On simule un fichier avec StringIO
out_s = StringIO()
# Écriture dans le flux
for o in data:
print 'WRITING: %s (%s)' % (o.name, o.name_backwards)
pickle.dump(o, out_s)
out_s.flush()
# Mise en place d'un flux lisible
in_s = StringIO(out_s.getvalue())
# Lecture des données
while True:
try:
o = pickle.load(in_s)
except EOFError:
break
else:
print 'READ: %s (%s)' % (o.name, o.name_backwards)
Cet exemple simule un flux en utilisant une instance de StringIO comme buffer, c'est pour cette raison que nous utilisons un petit truc pour initialiser le flux qui sera lu. Une base de données simpliste pourrait également utiliser pickle pour stocker des objets bien que le module shelve soit sans doute plus approprié pour cet usage.
En plus de stocker des données, pickle est également très utile pour la communication inter-processus. Par exemple, en utilisant os.fork() et os.pipe(), il est possible d'établir des processus opérationnels (worker processes) qui liraient des instructions depuis un pipe et écriraient leurs résultats dans un autre. Le code principal pour gérer les processus opérationnels et les communications avec et entre eux, émissions des instructions et récupérations des résultats, pourrait être réutilisé puisque les objets d'instruction et de résultat n'ont pas besoin d'être d'une classe particulière. Si vous utilisez des sockets ou des flux, il ne faut oublier de vider le buffer avec flush après chaque sérialisation avec dump() afin d'envoyer les données à travers la connexion.
Problèmes à la reconstruction des objets
Lorsque que vous travaillerez avec vos propres classes, vous devrez vous assurez que la classe qui va être reconstruite existe également dans l'espace du processus qui lit les données "picklées". En effet, seules les données de l'instance sont sérialisées et pas la définition de la classe. Le constructeur est retrouvé grâce au nom de la classe. Dans cette exemple, l'instance d'une classe est écrite dans un fichier :
try:
import cPickle as pickle
except:
import pickle
import sys
class SimpleObject(object):
def __init__(self, name):
self.name = name
l = list(name)
l.reverse()
self.name_backwards = ''.join(l)
return
if __name__ == '__main__':
data = []
data.append(SimpleObject('pickle'))
data.append(SimpleObject('cPickle'))
data.append(SimpleObject('last'))
try:
filename = sys.argv[1]
except IndexError:
raise RuntimeError('Please specify a filename as an argument to %s' % sys.argv[0])
out_s = open(filename, 'wb')
try:
# Write to the stream
for o in data:
print 'WRITING: %s (%s)' % (o.name, o.name_backwards)
pickle.dump(o, out_s)
finally:
out_s.close()
Si ce script est lancé, il créera un fichier dont le nom est passé en argument de la ligne de commande.
Une tentative, basique, de charger les données depuis le fichiers pourrait s'implémenter ainsi :
try:
import cPickle as pickle
except:
import pickle
import pprint
from StringIO import StringIO
import sys
try:
filename = sys.argv[1]
except IndexError:
raise RuntimeError('Please specify a filename as an argument to %s' % sys.argv[0])
in_s = open(filename, 'rb')
try:
# Read the data
while True:
try:
o = pickle.load(in_s)
except EOFError:
break
else:
print 'READ: %s (%s)' % (o.name, o.name_backwards)
finally:
in_s.close()
Cependant ce script échouera car la classe SimpleObject n'est pas accessible pour celui-ci. Afin que ce code fonctionne il convient d'importer SimpleObject depuis le fichier du script qui écrit les données. Ajoutons donc :
from pickle_dump_to_file_1 import SimpleObject
à la fin des importations.
Des précautions particulières sont à prendre quand on veut "pickler" des types données que ne le peuvent pas (les sockets, les handles des fichiers, les connections à une base de données, etc.). Les classes qui utilisent de telles données ne pourront pas être sérialisées et doivent définir les fonctions __getstate__() et __setstate__() qui renverront une image de l'état de l'instance qu'elle soit sérialisée. Les classes usant les nouveaux mécanisme de Python peuvent également définir la méthode getnewargs(), qui doit retourner les arguments à destination de l'allocateur de mémoire pour la classe(C.__new__()). Ces fonctionnalités sont couvertes plus en détails par la documentation officielle.
Voir aussi
La documentation officielle de ce module.
PyMOTW pour le module shelve.
Pickle: An interesting stack language.
Un article sur pickle par Alexandre Vassalotti
pickle and cPickle – Python object serialization
La version originale de ce PyMOTW.
Publié le mardi 4 août 2009 par Thomas