pickle
--- Module de sérialisation d'objets Python¶
Code source : Lib/pickle.py
Le module pickle
implémente des protocoles binaires de sérialisation et dé-sérialisation d'objets Python. Le pickling est le procédé par lequel une hiérarchie d'objets Python est convertie en flux d'octets. unpickling est l'opération inverse, par laquelle un flux d'octets (à partir d'un binary file ou bytes-like object) est converti en hiérarchie d'objets. Pickling (et unpickling) sont alternativement connus sous les termes de "sérialisation", de "marshalling" 1 ou encore de "flattening". Cependant pour éviter la confusion les termes utilisés ici sont pickling et unpickling.
Avertissement
Le module pickle
n'est pas sécurisé. Ne désérialisez des objets qu'à partir de sources fiables.
Il est possible de produire des données binaires qui exécutent du code arbitraire lors de leur désérialisation. Ne désérialisez jamais des données provenant d'une source non fiable, ou qui pourraient avoir été modifiées.
Pensez au module hmac
pour signer des données afin de s'assurer qu'elles n'ont pas été modifiées.
Des formats de sérialisation plus sûrs, comme json
, peuvent se révéler plus adaptés si vous travaillez sur des données qui ne sont pas fiables. Voir Comparaison avec json.
Relations aux autres modules python¶
Comparaison avec marshal
¶
Python possède un module de bas niveau en sérialisation appelé marshal
, mais en général il est préférable d'utiliser pickle
pour sérialiser des objets Python. marshal
existe principalement pour gérer les fichiers Python en .pyc
.
Le module pickle
diffère du module marshal
sur plusieurs aspects :
Le module
pickle
garde la trace des objets qu'il a déjà sérialisés, pour faire en sorte que les prochaines références à cet objet ne soient pas sérialisées à nouveau.marshal
ne le fait pas.Ça a des implications sur les objets partagés et les objets récursifs. Les objets récursifs sont des objets qui contiennent des références à eux-mêmes. Ceux-ci ne sont pas gérées par marshal : lui donner un objet récursif va le faire planter. Un objet est partagé lorsque que plusieurs références pointent dessus, depuis différents endroits dans la hiérarchie sérialisée. Le module
pickle
repère ces partages et ne stocke ces objets qu'une seule fois. Les objets partagés restent ainsi partagés, ce qui peut être très important pour les objets muables.marshal
ne peut être utilisé pour la sérialisation et l'instanciation de classes définies par les utilisateurs.pickle
peut sauvegarder et restaurer les instances de classes de manière transparente. Cependant la définition de classe doit être importable et lancée dans le même module et de la même manière que lors de son importation.Aucune garantie n'est offerte sur la portabilité du format
marshal
entre différentes versions de Python. Sa fonction première étant la gestion des fichiers.pyc
, les développeurs se réservent le droit de changer le format de sérialisation de manière non-rétrocompatible si besoin était. Il est garanti que le formatpickle
restera compatible avec les versions futures de Python, pourvu que vous choisissiez un protocole de sérialisation adapté. De plus, il masque les différences entre les types Python 2 et Python 3, pour le cas où il s'agit de désérialiser en Python 3 des données sérialisées en Python 2.
Comparaison avec json
¶
There are fundamental differences between the pickle protocols and JSON (JavaScript Object Notation):
pickle est un format binaire, tandis que JSON est un format textuel (constitué de caractères Unicode et généralement encodé en UTF-8) ;
JSON peut être lu par une personne, contrairement à pickle ;
JSON offre l'interopérabilité avec de nombreux outils en dehors de l'écosystème Python, alors que pickle est propre à Python ;
Par défaut, JSON n'est capable de sérialiser qu'un nombre limité de types natifs Python, et ne prend pas en charge les classes définies par l'utilisateur. Le format pickle peut représenter une multitude de types d'objets, dont beaucoup automatiquement, grâce à une utilisation fine des possibilités d'introspection de Python ; on peut traiter les cas les plus complexes en implémentant des méthodes de sérialisation propres à une classe ;
Contrairement à pickle, la désérialisation de données JSON n'ouvre pas en soi une vulnérabilité à l'exécution de code arbitraire.
Voir aussi
Le module json
de la bibliothèque standard permet la sérialisation et désérialisation au format JSON.
Format du flux de données¶
Le format de données employé par pickle
est propre à Python, avec l'avantage qu'aucune restriction n'est imposée par des standards externes comme JSON ou XDR (qui ne peuvent pas représenter le partage de références). Cependant, cela signifie que des programmes écrits en d'autres langages que Python peuvent échouer à reconstituer les objets sérialisés.
Le format binaire pickle
est, par défaut, une représentation assez compacte des objets. Il est possible de compresser efficacement les données sérialisées.
Le module pickletools
contient des outils servant à analyser les flux de données générés par pickle
. Le code source de pickletools
contient des commentaires détaillés sur les opcodes employés par les protocoles pickle.
Il existe actuellement 6 protocoles différents pour la sérialisation. Les protocoles portant les numéros les plus grands sont les derniers ajoutés, et nécessitent en conséquence des versions de Python plus récentes.
Le protocole 0 est le format originel, humainement lisible. Il est rétrocompatible avec les versions les plus anciennes de Python.
Le protocole 1 est un ancien format binaire, aussi compatible avec les versions anciennes.
Protocol version 2 was introduced in Python 2.3. It provides much more efficient pickling of new-style classes. Refer to PEP 307 for information about improvements brought by protocol 2.
Le protocole 3 a été introduit en Python 3.0. Il gère les objets
bytes
, et ne permet pas la désérialisation par Python 2.x. Il fut le protocole par défaut de Python 3.0 à Python 3.7.Le protocole 4 est apparu en Python 3.4. Il prend en charge les objets de très grande taille ainsi que la sérialisation d'une plus grande variété d'objets, et optimise le format. Il est le protocole par défaut depuis Python 3.8. Voir la PEP 3154 pour plus d'informations sur les améliorations apportées par le protocole 4.
Le protocole 5 a été ajouté en Python 3.8 afin de permettre le transfert des données en marge de la sérialisation elle-même. Il a également accéléré les opérations sur les données à sérialiser. Reportez-vous à la PEP 574 pour les détails relatifs aux améliorations apportées par le protocole 5.
Note
La sérialisation est un problème plus simple que la persistance des données en général. Le module pickle
lit et écrit des objets fichiers-compatibles, mais ne s'occupe pas du problème de donner un nom à des objets persistants, ni de gérer l'accès par différents processus en parallèle à ces objets. pickle
se contente de transformer des objets complexes en flux d'octets, et lire ces flux d'octets par la suite pour reconstruire des objets avec la même structure. Si l'on peut bien sûr écrire les flux d'octets dans un fichier, rien n'empêche de les transférer à travers un réseau, ou bien de les stocker dans une base de données. Voir le module shelve
pour une interface simple qui sérialise et désérialise les objets dans des bases de données de style DBM.
Interface du module¶
Pour sérialiser un objet, contenant éventuellement d'autres objets, appelez tout simplement la fonction dumps()
. La fonction loads()
, quant à elle, désérialise un flux de données. Pour un contrôle plus fin des opérations de sérialisation ou désérialisation, créez un objet Pickler
ou Unpickler
.
Le module pickle
définit les constantes suivantes :
-
pickle.
HIGHEST_PROTOCOL
¶ Entier qui donne la version du protocole le plus récent qui soit disponible. Ce nombre peut être passé comme paramètre protocol aux fonctions
dump()
etdumps()
ainsi qu'au constructeur de la classePickler
.
-
pickle.
DEFAULT_PROTOCOL
¶ Entier qui donne la version du protocole employé par défaut pour la sérialisation. Il peut être moindre que
HIGHEST_PROTOCOL
. La valeur actuelle est 4, sachant que le protocole correspondant a été introduit en Python 3.4 et n'est pas compatible avec les versions antérieures.Modifié dans la version 3.0: Le protocole par défaut est devenu le protocole 3.
Modifié dans la version 3.8: Le protocole par défaut est devenu le protocole 4.
Le module pickle
contient quelques fonctions pour faciliter la sérialisation.
-
pickle.
dump
(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)¶ Écrit la représentation sérialisée de l'objet obj dans l'objet fichier-compatible file, qui doit être ouvert. Ceci est l'équivalent de
Pickle(file, protocol).dump(obj)
.Les arguments file, protocol, fix_imports et buffer_callback sont identiques à ceux du constructeur de la classe
Pickler
.Modifié dans la version 3.8: ajout de l'argument buffer_callback.
-
pickle.
dumps
(obj, protocol=None, *, fix_imports=True, buffer_callback=None)¶ Renvoie la représentation sérialisée de obj sous forme de
bytes
, au lieu de l'écrire dans un fichier.Les arguments protocol, fix_imports et buffer_callback sont identiques à ceux du constructeur de la classe
Pickler
.Modifié dans la version 3.8: ajout de l'argument buffer_callback.
-
pickle.
load
(file, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)¶ Charge la représentation sérialisée d'un objet depuis l'objet fichier-compatible ouvert file, et renvoie l'objet reconstitué obtenu. Ceci est l'équivalent de
Unpickler(file).load()
.La version du protocole utilisée pour la sérialisation est détectée automatiquement, d'où l'absence d'un argument. Les octets situés après la représentation sérialisée de l'objet sont ignorés.
Les arguments file, fix_imports, encoding, errors, strict et buffers sont identiques à ceux du constructeur de la classe
Unpickler
.Modifié dans la version 3.8: Ajout de l'argument buffers.
-
pickle.
loads
(data, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)¶ Renvoie l'objet reconstitué à partir de la représentation sérialisée data, qui doit être fournie sous la forme d'un bytes-like object.
La version du protocole utilisée pour la sérialisation est détectée automatiquement, d'où l'absence d'un argument. Les octets situés après la représentation sérialisée de l'objet sont ignorés.
Les arguments file, fix_imports, encoding, errors, strict et buffers sont identiques à ceux du constructeur de la classe
Unpickler
.Modifié dans la version 3.8: Ajout de l'argument buffers.
Le module pickle
définit trois types d'exceptions :
-
exception
pickle.
PickleError
¶ Classe mère commune aux autres exceptions de sérialisation. Elle hérite de
Exception
.
-
exception
pickle.
PicklingError
¶ Exception levée lorsqu'un objet impossible à sérialiser est rencontré par un
Pickler
. Elle hérite dePickleError
.Lisez Quels objets sont sérialisables ? pour en savoir plus sur les types d'objets qui peuvent être sérialisés.
-
exception
pickle.
UnpicklingError
¶ Exception levée lorsqu'un flux binaire ne peut pas être désérialisé, par exemple s'il est corrompu ou en cas de violation de la sécurité. Elle hérite de
PickleError
.Veuillez noter que d'autres exceptions peuvent être levées durant la désérialisation, comme
AttributeError
,EOFError
,ImportError
etIndexError
(liste non-exhaustive).
Le module pickle
exporte trois classes : Pickler
, Unpickler
et PickleBuffer
.
-
class
pickle.
Pickler
(file, protocol=None, *, fix_imports=True, buffer_callback=None)¶ Classe d'objets qui implémentent la sérialisation vers un flux binaire.
L'argument optionnel protocol détermine la version du protocole de sérialisation à employer, entre 0 et
HIGHEST_PROTOCOL
. La valeur par défaut est celle deDEFAULT_PROTOCOL
. Avec un nombre strictement négatif, c'estHIGHEST_PROTOCOL
qui est utilisé.L'argument file peut être un fichier sur disque ouvert en mode binaire pour l'écriture, une instance de la classe
io.BytesIO
, ou plus généralement un objet quelconque qui possède une méthodewrite()
acceptant d'être appelée sur un argument unique de typebytes
.Si fix_imports est vrai et protocol est inférieur ou égal à 2, les noms des modules sont reliés par pickle aux anciens noms qui avaient cours en Python 2, afin que le flux sérialisé soit lisible aussi bien par Python 2 que Python 3.
Si buffer_callback vaut
None
(comme par défaut), les vues de tampon sont sérialisées dans file avec le reste du flux.Dans le cas où le buffer_callback n'est pas
None
, il doit pouvoir être appelé un nombre quelconque de fois avec une vue d'un tampon. S'il renvoie une valeur évaluée comme fausse (telle queNone
), le tampon est considéré en marge de la sérialisation (ou « hors-bande »), sinon il est sérialisé dans le flux binaire.Une erreur se produit si buffer_callback vaut autre chose que
None
et protocol est 4 ou inférieur, ouNone
.Modifié dans la version 3.8: ajout de l'argument buffer_callback.
-
dump
(obj)¶ Écrit la représentation sérialisée de l'objet obj dans le fichier ouvert passé au constructeur.
-
persistent_id
(obj)¶ Ne fait rien par défaut. Cette méthode est destinée à être implémentée par une classe fille.
Si
persistent_id()
renvoieNone
, obj est sérialisé normalement. Toute autre valeur est reprise par lePickler
comme ID persistant pour obj. Le sens de cet ID persistant doit être défini parUnpickler.persistent_load()
. Veuillez noter que la valeur renvoyée parpersistent_id()
ne peut pas porter elle-même d'ID persistant.La section Persistance d'objets externes donne des détails et exemples.
-
dispatch_table
¶ La table de distribution d'un sérialiseur est un tableau associatif dont les clés sont des classes et les valeurs, des fonctions de réduction. La manière la plus directe de déclarer une fonction de réduction est la fonction
copyreg.pickle()
. Une fonction de réduction prend un unique argument, qui doit être de la classe en question, et obéit à la même interface qu'une méthode__reduce__()
.Lorsqu'un sérialiseur ne possède pas l'attribut
dispatch_table
, comme c'est le cas par défaut, il utilise le tableau global du modulecopyreg
. Afin de personnaliser l'opération de sérialisation pour un sérialiseur particulier, on peut affecter à son attributdispatch_table
un objet compatible avec les dictionnaires. Une autre possibilité est de définir l'attributdispatch_table
dans une classe fille dePickler
. Sa valeur sera alors utilisée pour toutes les instances de cette classe fille.Voir Tables de distribution pour des exemples d'utilisation.
Nouveau dans la version 3.3.
-
reducer_override
(self, obj)¶ Réducteur spécial, qui peut se définir dans une classe fille de
Pickler
. Cette méthode doit se conformer à l'interface des méthodes__reduce__()
. Elle prend en général le pas sur les réducteurs contenus dans l'attributdispatch_table
. Cependant, elle peut choisir de renvoyerNotImplemented
pour quedispatch_table
soit utilisé à la place.Voir Réduction personnalisée pour les types, fonctions et autres objets pour un exemple détaillé.
Nouveau dans la version 3.8.
-
fast
¶ Cet attribut est obsolète. Une valeur vraie (« mode rapide ») désactive la mémorisation des objets au fur et à mesure de leur sérialisation, qui permet habituellement de représenter les doublons par une unique référence. Cette option accélère la sérialisation en évitant des opcodes PUT superflus. Elle ne doit pas être utilisée sur un objet contenant une référence à lui-même, car le
Pickler
entre alors dans une récursion infinie.Utilisez plutôt
pickletools.optimize()
pour obtenir des données sérialisées plus compactes.
-
-
class
pickle.
Unpickler
(file, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)¶ Les objets de cette classe sont des désérialiseurs, qui lisent un flux de données pour le convertir en objet.
Il n'y a nul besoin d'argument protocol. La version du protocole avec lequel sont encodées les données est déterminée automatiquement.
L'argument file doit posséder trois méthodes qui proviennent de l'interface de
io.BufferedIOBase
. Ce sont :read()
, prenant un entier,readinto()
, prenant un tampon, etreadline()
, sans arguments. file peut donc être aussi bien un fichier sur disque ouvert en mode lecture binaire qu'un objetio.BytesIO
, ou un objet quelconque vérifiant ces critères.Les paramètres facultatifs fix_imports, encoding et errors sont dédiés à la compatibilité de la désérialisation avec les flux binaires générés par Python 2. Si fix_imports est vrai, pickle tente de modifier les anciens noms des modules que l'on trouve en Python 2, pour les remplacer par ceux en usage en Python 3. Les paramètres encoding et errors contrôlent la façon de décoder les chaînes de caractères 8 bits. Leurs valeurs par défaut respectives sont
'ASCII'
et'strict'
. encoding peut être mis à'bytes'
pour lire des chaînes d'octets en tant que bytes. Il doit être mis à'latin1'
pour désérialiser des tableaux NumPy ou des instances dedatetime
,date
ettime
sérialisées par Python 2.Si buffers vaut None (comme par défaut), toutes les données nécessaires à la désérialisation doivent être contenues dans le flux binaire. Ceci signifie que l'argument buffer_callback valait None lors de la construction du
Pickler
(ou dans l'appel àdump()
oudumps()
).If buffers is not None, it should be an iterable of buffer-enabled objects that is consumed each time the pickle stream references an out-of-band buffer view. Such buffers have been given in order to the buffer_callback of a Pickler object.
Modifié dans la version 3.8: Ajout de l'argument buffers.
-
load
()¶ Lit la représentation sérialisée d'un objet depuis le fichier ouvert passé au constructeur, et reconstitue l'objet qui y est stocké, avec tous les objets qu'il contient. Les octets situés au-delà de la fin de la représentation binaire sont ignorés.
-
persistent_load
(pid)¶ Par défaut, cette méthode lève une exception
UnpicklingError
.Si elle est définie autrement dans une sous-classe,
persistent_load()
doit renvoyer l'objet correspondant à l'ID persistant pid. Si celui-ci est invalide, elle doit lever une exceptionUnpicklingError
.La section Persistance d'objets externes donne des détails et exemples.
-
find_class
(module, name)¶ Importe module si besoin, et renvoie l'objet du nom name qu'il contient. module et name sont des chaînes de caractères (classe
str
). Contrairement à ce que son nom laisse penser,find_class()
est également appelée pour trouver les fonctions.Les classes filles peuvent redéfinir cette méthode pour restreindre la désérialisation à certains types d'objets ou à d'autres conditions, notamment en vue de réduire les risques de sécurité. Voir Restriction des noms dans l'espace de nommage global pour plus de détails.
Lève un événement d'audit
pickle.find_class
avec les argumentsmodule
etname
.
-
-
class
pickle.
PickleBuffer
(buffer)¶ Encapsule un objet tampon contenant des données sérialisables. buffer doit être un objet prenant en charge le protocole tampon, comme un objet octet-compatible ou un tableau n-dimensionnel.
Les objets
PickleBuffer
savent gérer le protocole tampon. Il est donc possible de les passer à d'autres API qui attendent un tampon, commememoryview
.Les objets
PickleBuffer
ne peuvent être sérialisés qu'avec le protocole 5 ou supérieur. Ils sont susceptibles d'être sérialisés hors-bande.Nouveau dans la version 3.8.
-
raw
()¶ Renvoie une
memoryview
de l'espace mémoire sous-jacent à ce tampon. La memoryview renvoyée est unidimensionnelle et C-contiguë. Elle a le formatB
(octets sans signe).BufferError
est levée si le tampon n'est ni C-contigu, ni Fortran-contigu.
-
release
()¶ Release the underlying buffer exposed by the PickleBuffer object.
-
Quels objets sont sérialisables ?¶
Les objets des types suivants peuvent être sérialisés :
None
,True
, andFalse
integers, floating point numbers, complex numbers
strings, bytes, bytearrays
tuples, lists, sets, and dictionaries containing only picklable objects
functions defined at the top level of a module (using
def
, notlambda
)les fonctions natives définies au plus haut niveau dans un module ;
classes that are defined at the top level of a module
instances of such classes whose
__dict__
or the result of calling__getstate__()
is picklable (see section Sérialisation des instances d'une classe for details).
Si vous essayez de sérialiser un objet qui ne peut pas l'être, une exception de type PicklingError
est levée. Lorsque cela se produit, il est possible qu'un certain nombre d'octets aient déjà été écrits dans le fichier ou flux. La sérialisation d'une structure de donnée avec de nombreux niveaux d'imbrication peut lever une exception RecursionError
. Pour augmenter la limite (avec précaution), voir sys.setrecursionlimit()
.
Note that functions (built-in and user-defined) are pickled by "fully qualified" name reference, not by value. 2 This means that only the function name is pickled, along with the name of the module the function is defined in. Neither the function's code, nor any of its function attributes are pickled. Thus the defining module must be importable in the unpickling environment, and the module must contain the named object, otherwise an exception will be raised. 3
Similarly, classes are pickled by named reference, so the same restrictions in
the unpickling environment apply. Note that none of the class's code or data is
pickled, so in the following example the class attribute attr
is not
restored in the unpickling environment:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
These restrictions are why picklable functions and classes must be defined in the top level of a module.
De même, lorsque les instances d'une certaine classe sont sérialisées, seuls les attributs de l'instance sont inclus, mais pas le code de leur classe ni les données qu'elle pourrait contenir. Ceci est intentionnel : vous pouvez corriger des bogues dans une classe ou ajouter des méthodes, et désérialiser malgré tout des objets instanciés avec une version plus ancienne de la classe. Si vous stockez des objets destinés être conservés pendant longtemps, et que leur classe est susceptible de connaître de nombreuses évolutions, il peut s'avérer utile d'associer aux objets un numéro de version afin que des conversions puissent être implémentées dans la méthode __setstate__()
de la classe.
Sérialisation des instances d'une classe¶
Dans cette section sont décrits les mécanismes généraux qui s'offrent à vous pour définir, personnaliser et contrôler la manière dont les instances d'une classe sont sérialisées et désérialisées.
Dans la plupart des cas, il n'y a besoin de rien pour que les instances d'une classe puissent être sérialisées. Par défaut, pickle accède à la classe et aux attributs de l'instance par introspection. Lorsqu'une instance est désérialisée, sa méthode __init__()
n'est normalement pas appelée. Une nouvelle instance est créée sans être initialisée, et ses attributs sont simplement restaurés à partir des valeurs conservées. En d'autres termes, les opérations sont celles qu'effectue le code suivant :
def save(obj):
return (obj.__class__, obj.__dict__)
def load(cls, attributes):
obj = cls.__new__(cls)
obj.__dict__.update(attributes)
return obj
Les classes peuvent personnaliser le comportement par défaut en définissant des méthodes spéciales :
-
object.
__getnewargs_ex__
()¶ Dans les protocoles 2 et suivants, les classes peuvent personnaliser les valeurs passées à la méthode
__new__()
lors de la désérialisation. Elles le font en définissant une méthode__getnewargs_ex__()
qui renvoie un couple(args, kwargs)
, où args est un n-uplet des arguments positionnels et kwargs un dictionnaire des arguments nommés qui seront passés à__new__()
— autrement dit, l'appel seraclasse.__new__(*args, **kwargs)
.Définissez cette méthode seulement si la méthode
__new__()
de votre classe demande des arguments nommés. Dans le cas contraire, mieux vaut définir__getnewargs__()
pour préserver la compatibilité avec les protocoles anciens.Modifié dans la version 3.6:
__getnewargs_ex__()
est désormais appelée dans les protocoles 2 et 3.
-
object.
__getnewargs__
()¶ Comme
__getnewargs_ex__()
, mais ne permet que les arguments positionnels. Cette méthode doit renvoyer le n-upletargs
des arguments passés à__new__()
lors de la désérialisation : l'appel seraclasse.__new__(*args)
.Si
__getnewargs_ex__()
est définie, elle prend la priorité et__getnewargs__()
n'est jamais appelée.Modifié dans la version 3.6: Auparavant,
__getnewargs__()
était appelée au lieu de__getnewargs_ex__()
dans les protocoles 2 et 3.
-
object.
__getstate__
()¶ Classes can further influence how their instances are pickled; if the class defines the method
__getstate__()
, it is called and the returned object is pickled as the contents for the instance, instead of the contents of the instance's dictionary. If the__getstate__()
method is absent, the instance's__dict__
is pickled as usual.
-
object.
__setstate__
(state)¶ Lors de la désérialisation, l'état de l'instance est passé à la méthode
__setstate__()
, si elle est définie (l'objet state n'a pas besoin d'être un dictionnaire). Si elle ne l'est pas, les attributs de l'objet sont tirés de l'état, qui dans ce cas doit être obligatoirement un dictionnaire.Note
Si
__getstate__()
renvoie une valeur fausse,__setstate__()
ne sera pas appelée à la désérialisation.
Voir Traitement des objets à état pour plus d'informations sur __getstate__()
et __setstate__()
.
Note
Lors de la désérialisation, des méthodes comme __getattr__()
, __getattribute__()
et __setattr__()
sont susceptibles d'être appelées sur l'instance. Si ces méthodes reposent sur des invariants internes à l'objet, leur classe doit les initialiser dans la méthode __new__()
, puisque la méthode __init__()
n'est pas appelée.
Comme nous le verrons, pickle ne fait pas directement appel aux méthodes ci-dessus. En réalité, elles font partie du protocole de copie, qui implémente la méthode spéciale __reduce__()
. Ce protocole constitue une interface unifiée pour l'accès aux données nécessaires à la sérialisation comme à la copie 4.
Bien que la méthode __reduce__()
ouvre davantage de possibilités, elle conduit plus facilement à des erreurs. C'est pourquoi les auteurs de classes sont encouragés à utiliser lorsque c'est possible l'interface de plus haut niveau avec __getnewargs_ex__()
, __getstate__()
et __state__()
. Cependant, il existe des cas où l'on ne peut pas se passer de __reduce__()
, ou bien elle permet une sérialisation plus efficace.
-
object.
__reduce__
()¶ Voici l'interface de la méthode
__reduce__()
. Elle ne prend aucun argument et renvoie soit une chaîne de caractères, soit (c'est conseillé) un n-uplet. On appelle souvent l'objet renvoyé « valeur de réduction ».If a string is returned, the string should be interpreted as the name of a global variable. It should be the object's local name relative to its module; the pickle module searches the module namespace to determine the object's module. This behaviour is typically useful for singletons.
Si c'est un n-uplet qui est renvoyé, ses éléments sont interprétés dans l'ordre comme suit. Les deux premiers éléments sont obligatoires, les quatre suivants sont facultatifs et peuvent être simplement omis, ou bien mis à
None
. Les éléments sont, dans l'ordre :Un objet appelable qui sera appelé pour créer l'objet initial.
Un n-uplet d'arguments passés à cet objet appelable. Donnez un n-uplet vide si l'objet appelable n'accepte pas d'arguments.
L'état de l'objet, qui sera passé à la méthode
__setstate__()
comme vu précédemment. Si la méthode n'existe pas, cet élément doit être un dictionnaire, et ses éléments compléteront l'attribut__dict__
.Un itérateur (non pas une séquence). Les éléments qu'il fournit sont ajoutés à l'objet un par un avec la méthode
append()
, ou bien plusieurs à la fois avec la méthodeextend()
. Ceci est principalement utile aux les classes héritant delist
, mais peut aussi servir sur d'autres classes, la seule contrainte étant qu'elles implémententappend()
etextend()
avec les bonnes signatures (l'une ou l'autre de ces méthodes est utilisée selon la version du protocole pickle et le nombre d'éléments à ajouter, c'est pourquoi elles doivent être définies toutes les deux).Un itérateur (non pas une séquence). Les éléments qu'il fournit doivent être des couples
(clé, valeur)
. Ils sont ajoutés dans l'objet par affectation aux clés :objet[clé] = valeur
. Ceci est principalement utile aux classes héritant dedict
, mais peut servir à d'autres classes à la seule condition qu'elles implémentent la méthode__setitem__()
.Un objet appelable qui puisse recevoir en arguments l'objet et son état. Ceci permet de redéfinir le processus de reconstruction des attributs pour un objet en particulier, outrepassant la méthode
__setstate__()
. Si cet objet appelable est fourni,__setstate__()
n'est pas appelée.Nouveau dans la version 3.8: ajout du sixième élément.
-
object.
__reduce_ex__
(protocol)¶ Il est également possible de définir une méthode
__reduce_ex__()
. La seule différence est qu'elle prend la version du protocole en argument. Si elle est définie, elle prend le pas sur__reduce__()
. De plus,__reduce__()
devient automatiquement un alias pour sa version étendue. Cette méthode est principalement destinée à renvoyer des valeurs de réduction compatibles avec les versions anciennes de Python.
Persistance d'objets externes¶
Pour les besoins de la persistance, pickle
permet des références à des objets en dehors du flux sérialisé. Ils sont identifiés par un ID persistant. Le protocole 0 requiert que cet ID soit une chaîne de caractères alphanumériques 5. Les suivants autorisent un objet quelconque.
pickle
délègue la résolution des ID à des méthodes définies par l'utilisateur sur les objets sérialiseurs et désérialiseurs, à savoir persistent_id()
et persistent_load()
.
Pour affecter à des objets leurs ID persistants provenant d'une source externe, le sérialiseur doit posséder une méthode persistent_id()
qui prend un objet et renvoie soit None
, soit son ID. Si cette méthode renvoie None
, l'objet est sérialisé de la manière habituelle. Si un ID est renvoyé, sous forme de chaîne de caractères, c'est cette chaîne qui est sérialisée et elle est marquée de manière spéciale pour être reconnue comme un ID persistant.
Pour désérialiser des objets identifiés par un ID externe, un désérialiseur doit posséder une méthode persistent_load()
qui prend un ID et renvoie l'objet qu'il désigne.
Voici un exemple complet qui montre comment sérialiser des objets externes en leur affectant des ID persistants.
# Simple example presenting how persistent ID can be used to pickle
# external objects by reference.
import pickle
import sqlite3
from collections import namedtuple
# Simple class representing a record in our database.
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# Instead of pickling MemoRecord as a regular class instance, we emit a
# persistent ID.
if isinstance(obj, MemoRecord):
# Here, our persistent ID is simply a tuple, containing a tag and a
# key, which refers to a specific record in the database.
return ("MemoRecord", obj.key)
else:
# If obj does not have a persistent ID, return None. This means obj
# needs to be pickled as usual.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# This method is invoked whenever a persistent ID is encountered.
# Here, pid is the tuple returned by DBPickler.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# Fetch the referenced record from the database and return it.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# Always raises an error if you cannot return the correct object.
# Otherwise, the unpickler will think None is the object referenced
# by the persistent ID.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# Initialize and populate our database.
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
tasks = (
'give food to fish',
'prepare group meeting',
'fight with a zebra',
)
for task in tasks:
cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))
# Fetch the records to be pickled.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# Save the records using our custom DBPickler.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# Update a record, just for good measure.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# Load the records from the pickle data stream.
file.seek(0)
memos = DBUnpickler(file, conn).load()
print("Unpickled records:")
pprint.pprint(memos)
if __name__ == '__main__':
main()
Tables de distribution¶
Pour personnaliser la sérialisation d'une classe à un endroit particulier sans affecter le reste du code, on peut créer un sérialiseur avec une table de distribution spécifique.
La table de distribution gérée par le module copyreg
est disponible sous le nom copyreg.dispatch_table
. On peut donc utiliser une copie modifiée de copyreg.dispatch_table
comme table spécifique à un sérialiseur.
Par exemple
f = io.BytesIO()
p = pickle.Pickler(f)
p.dispatch_table = copyreg.dispatch_table.copy()
p.dispatch_table[SomeClass] = reduce_SomeClass
crée une instance de la classe pickle.Pickler
avec une table de distribution propre qui traite la classe SomeClass
de manière spécifique. Le code :
class MyPickler(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)
does the same, but all instances of MyPickler
will by default
share the same dispatch table. The equivalent code using the
copyreg
module is
copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)
Traitement des objets à état¶
L'exemple suivant illustre comment modifier la sérialisation pour une classe. La classe TextReader
ouvre un fichier de texte, et sa méthode readline()
renvoie le numéro de la ligne suivante et son contenu chaque fois qu'elle est appelée. Si une instance de TextReader
est sérialisée, tous les attributs sauf le fichier ouvert sont enregistrés. Lorsque l'instance est désérialisée, le fichier est rouvert et la lecture reprend là où elle s'était arrêtée. Ceci est implémenté à travers les méthodes __setstate__()
et __getstate__()
.
class TextReader:
"""Print and number lines in a text file."""
def __init__(self, filename):
self.filename = filename
self.file = open(filename)
self.lineno = 0
def readline(self):
self.lineno += 1
line = self.file.readline()
if not line:
return None
if line.endswith('\n'):
line = line[:-1]
return "%i: %s" % (self.lineno, line)
def __getstate__(self):
# Copy the object's state from self.__dict__ which contains
# all our instance attributes. Always use the dict.copy()
# method to avoid modifying the original state.
state = self.__dict__.copy()
# Remove the unpicklable entries.
del state['file']
return state
def __setstate__(self, state):
# Restore instance attributes (i.e., filename and lineno).
self.__dict__.update(state)
# Restore the previously opened file's state. To do so, we need to
# reopen it and read from it until the line count is restored.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# Finally, save the file.
self.file = file
Voici un exemple d'utilisation :
>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'
Réduction personnalisée pour les types, fonctions et autres objets¶
Nouveau dans la version 3.8.
Parfois, la simple utilisation de dispatch_table
n'offre pas assez de flexibilité. On peut vouloir changer la méthode de sérialisation selon d'autres critères que le type de l'objet, ou bien personnaliser la sérialisation des fonctions et des classes.
Dans ces cas, il est possible d'écrire une méthode reducer_override()
dans une classe fille de Pickler
. Cette méthode renvoie un n-uplet de réduction arbitraire (voir __reduce__()
). Elle peut aussi renvoyer NotImplemented
, auquel cas la méthode habituelle de réduction par table s'applique.
Si dispatch_table
et reducer_override()
sont tous les deux définis, reducer_override()
a la priorité.
Note
Pour des raisons de performance, la méthode reducer_override()
n'est jamais appelée sur None
, True
, False
, ainsi que les instances exactes (pas dérivées) de int
, float
, bytes
, str
, dict
, set
, frozenset
, list
et tuple
.
Voici un exemple simple qui implémente la sérialisation d'une classe :
import io
import pickle
class MyClass:
my_attribute = 1
class MyPickler(pickle.Pickler):
def reducer_override(self, obj):
"""Custom reducer for MyClass."""
if getattr(obj, "__name__", None) == "MyClass":
return type, (obj.__name__, obj.__bases__,
{'my_attribute': obj.my_attribute})
else:
# For any other object, fallback to usual reduction
return NotImplemented
f = io.BytesIO()
p = MyPickler(f)
p.dump(MyClass)
del MyClass
unpickled_class = pickle.loads(f.getvalue())
assert isinstance(unpickled_class, type)
assert unpickled_class.__name__ == "MyClass"
assert unpickled_class.my_attribute == 1
Tampons hors-bande¶
Nouveau dans la version 3.8.
Le module pickle
est parfois utilisé pour transférer des quantités énormes de données. Il peut devenir important de réduire les copies de mémoire au minimum pour préserver la performance et diminuer l'usage des ressources matérielles. Cependant, dans son contexte courant d'utilisation, le module pickle
effectue des copies depuis et vers le flux de données pour les besoins de la conversion de structures d'objets semblables à des graphes en flux séquentiels d'octets.
Cette contrainte peut être levée si le producteur (qui implémente les types d'objets à transférer) et le consommateur (qui implémente le système de communication) emploient les possibilités de transfert hors-bande offertes par les protocoles 5 et suivants.
API des producteurs¶
Les objets de grande taille à sérialiser doivent posséder une méthode __reduce_ex__()
qui, lorsqu'elle est appelée pour le protocole 5 ou plus, renvoie un objet PickleBuffer
au lieu d'un objet bytes
dès que la taille le justifie.
Les objets PickleBuffer
ne font que signaler que leur tampon permet le transfert hors-bande. Ils demeurent compatibles avec l'utilisation classique du module pickle
. Cependant, les consommateurs peuvent aussi choisir d'indiquer à pickle
qu'ils gèrent eux-mêmes ces tampons.
API des consommateurs¶
Un système de communication peut gérer de manière spécifique les objets PickleBuffer
générés lors de la sérialisation d'un réseau d'objets.
Du côté de l'expéditeur, il faut passer le paramètre buffer_callback à Pickler
(ou à dump()
ou dumps()
). Le buffer_callback sera appelé avec chaque PickleBuffer
généré lors de la sérialisation du réseau d'objets. Les tampons accumulés par le buffer_callback ne verront pas leurs données copiées dans le flux sérialisé. Il leur sera substitué un marqueur léger.
Du côté du receveur, il faut passer l'argument buffers à Unpickler
(ou load()
ou bien loads()
). buffers est un itérable des tampons passés à buffer_callback. Il doit fournir les tampons dans le même ordre que celui dans lequel ils ont été passés à buffer_callback. Les tampons fournis constituent la source des données qu'attendent les reconstructeurs des objets dont la sérialisation a abouti aux objets PickleBuffer
.
Entre expéditeur et receveur, le système de communication peut implémenter son propre mécanisme de transfert pour les tampons hors-bande. Parmi les optimisations possibles se trouvent l'utilisation de mémoire partagée et la compression spécifique au type de données.
Exemple¶
Voici un exemple trivial où est implémentée une classe fille de bytearray
capable de sérialisation hors-bande.
class ZeroCopyByteArray(bytearray):
def __reduce_ex__(self, protocol):
if protocol >= 5:
return type(self)._reconstruct, (PickleBuffer(self),), None
else:
# PickleBuffer is forbidden with pickle protocols <= 4.
return type(self)._reconstruct, (bytearray(self),)
@classmethod
def _reconstruct(cls, obj):
with memoryview(obj) as m:
# Get a handle over the original buffer object
obj = m.obj
if type(obj) is cls:
# Original buffer object is a ZeroCopyByteArray, return it
# as-is.
return obj
else:
return cls(obj)
Lorsqu'il rencontre le bon type, le reconstructeur (la méthode de classe _reconstruct
) renvoie directement le tampon original. Il s'agit d'une manière simple de simuler l'absence de copie dans cet exemple simpliste.
En tant que consommateur des objets, on peut les sérialiser de la manière classique. La désérialisation conduit alors à une copie.
b = ZeroCopyByteArray(b"abc")
data = pickle.dumps(b, protocol=5)
new_b = pickle.loads(data)
print(b == new_b) # True
print(b is new_b) # False: a copy was made
Mais en passant un buffer_callback et en donnant les tampons accumulés au désérialiseur, il n'y a plus de copie.
b = ZeroCopyByteArray(b"abc")
buffers = []
data = pickle.dumps(b, protocol=5, buffer_callback=buffers.append)
new_b = pickle.loads(data, buffers=buffers)
print(b == new_b) # True
print(b is new_b) # True: no copy was made
Cet exemple est limité par le fait que bytearray
effectue sa propre allocation de mémoire. Il n'est pas possible de créer un bytearray
sur la mémoire d'un autre objet. Cependant, certains types de données que l'on trouve dans des bibliothèques externes, comme les tableaux NumPy, n'ont pas cette limitation. Le passage hors-bande permet alors de n'effectuer aucune copie (ou bien de minimiser le nombre de copies) lors du transfert de données d'un système à l'autre ou d'un processus à l'autre.
Voir aussi
PEP 574 — Protocole pickle 5 avec données hors-bande
Restriction des noms dans l'espace de nommage global¶
Par défaut, la désérialisation importe toutes les classes ou fonctions que demande le flux de données. Dans bien des cas, ce comportement est inacceptable, puisqu'il permet de faire exécuter du code arbitraire dans l'environnement de désérialisation. Observez le résultat de ce flux de données fait-main lorsqu'il est lu :
>>> import pickle
>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
hello world
0
Dans cet exemple, le désérialiseur importe la fonction os.system()
et l'applique à la chaîne de caractères "echo hello world"
. C'est inoffensif, mais il n'est pas difficile d'imaginer des variantes qui endommageraient le système.
C'est pour cette raison qu'il s'avère parfois nécessaire de contrôler ce qui peut être désérialisé. Cela est possible en redéfinissant la méthode Unpickler.find_class()
. Contrairement à ce que son nom laisse penser, Unpickler.find_class()
est appelée pour tous les noms à chercher dans l'espace de nommage global, ce qui inclut les classes mais aussi les fonctions. Par ce biais, il est possible d'interdire complètement la résolution des noms globaux ou de la restreindre à un sous-ensemble que l'on considère sûr.
Voici un exemple de désérialiseur qui permet seulement la désérialisation d'un petit nombre de classes sûres du module builtins
.
import builtins
import io
import pickle
safe_builtins = {
'range',
'complex',
'set',
'frozenset',
'slice',
}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
# Only allow safe classes from builtins.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# Forbid everything else.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))
def restricted_loads(s):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()
A sample usage of our unpickler working has intended:
>>> restricted_loads(pickle.dumps([1, 2, range(15)]))
[1, 2, range(0, 15)]
>>> restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'os.system' is forbidden
>>> restricted_loads(b'cbuiltins\neval\n'
... b'(S\'getattr(__import__("os"), "system")'
... b'("echo hello world")\'\ntR.')
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'builtins.eval' is forbidden
Comme le montre l'exemple, il faut faire attention aux objets que l'on autorise à être désérialisés. Si la sécurité est une priorité, il peut être sage de se tourner vers des alternatives comme l'API du module xmlrpc.client
, ou des bibliothèques tierces.
Performances¶
Recent versions of the pickle protocol (from protocol 2 and upwards) feature
efficient binary encodings for several common features and built-in types.
Also, the pickle
module has a transparent optimizer written in C.
Exemples¶
Dans les cas les plus simples, utilisez les fonctions dump()
et load()
.
import pickle
# An arbitrary collection of objects supported by pickle.
data = {
'a': [1, 2.0, 3, 4+6j],
'b': ("character string", b"byte string"),
'c': {None, True, False}
}
with open('data.pickle', 'wb') as f:
# Pickle the 'data' dictionary using the highest protocol available.
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
Le code suivant lit les données qui viennent d'être sérialisées :
import pickle
with open('data.pickle', 'rb') as f:
# The protocol version used is detected automatically, so we do not
# have to specify it.
data = pickle.load(f)
Voir aussi
- Module
copyreg
Enregistre les fonctions de sérialisation pour les types définis par l'utilisateur.
- Module
pickletools
Outils pour travailler sur les données sérialisées et les analyser.
- Module
shelve
Bases de données indexées (module fondé sur
pickle
).- Module
copy
Copie superficielle ou récursive d'objets.
- Module
marshal
Sérialisation haute-performance des types natifs.
Notes
- 1
À ne pas confondre avec ce que fait le module
marshal
.- 2
C'est la raison pour laquelle les fonctions
lambda
ne peuvent pas être sérialisées : elles partagent toutes le même nom, à savoir<lambda>
.- 3
L'exception levée est généralement de type
ImportError
ouAttributeError
, mais ce n'est pas systématique.- 4
Le module
copy
fait appel à ce protocole pour les opérations de copie superficielle comme récursive.- 5
The limitation on alphanumeric characters is due to the fact the persistent IDs, in protocol 0, are delimited by the newline character. Therefore if any kind of newline characters occurs in persistent IDs, the resulting pickle will become unreadable.