pickle — 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. La sérialisation est le procédé par lequel une hiérarchie d'objets Python est convertie en flux d'octets. La désérialisation 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. Sérialisation (et désérialisation) sont aussi connus sous les termes de pickling, de "marshalling" 1 ou encore de "flattening".

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 format pickle 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

Il existe des différences fondamentales entre les protocoles de sérialisation définis par ce module et le format 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() et dumps() ainsi qu'au constructeur de la classe Pickler.

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.

Arguments fix_imports, encoding, errors, strict and buffers have the same meaning as in the Unpickler constructor.

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 de PickleError.

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 et IndexError (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 de DEFAULT_PROTOCOL. Avec un nombre strictement négatif, c'est HIGHEST_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éthode write() acceptant d'être appelée sur un argument unique de type bytes.

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 que None), 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, ou None.

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() renvoie None, obj est sérialisé normalement. Toute autre valeur est reprise par le Pickler comme ID persistant pour obj. Le sens de cet ID persistant doit être défini par Unpickler.persistent_load(). Veuillez noter que la valeur renvoyée par persistent_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 module copyreg. Afin de personnaliser l'opération de sérialisation pour un sérialiseur particulier, on peut affecter à son attribut dispatch_table un objet compatible avec les dictionnaires. Une autre possibilité est de définir l'attribut dispatch_table dans une classe fille de Pickler. 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(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'attribut dispatch_table. Cependant, elle peut choisir de renvoyer NotImplemented pour que dispatch_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, et readline(), sans arguments. file peut donc être aussi bien un fichier sur disque ouvert en mode lecture binaire qu'un objet io.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 de datetime, date et time 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() ou dumps()).

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 exception UnpicklingError.

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 arguments module et name.

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, comme memoryview.

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 format B (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, and False;

  • integers, floating-point numbers, complex numbers;

  • strings, bytes, bytearrays;

  • tuples, lists, sets, and dictionaries containing only picklable objects;

  • functions (built-in and user-defined) accessible from the top level of a module (using def, not lambda);

  • classes accessible from the top level of a module;

  • les instances de telles classes, à condition que leur __dict__, ou la valeur de retour de __getstate__(), soit sérialisable (voir Sérialisation des instances d'une classe pour plus d'informations).

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, not by value. 2 This means that only the function name is pickled, along with the name of the containing module and classes. 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 fully qualified name, 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 at 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 restore(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 sera classe.__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-uplet args des arguments passés à __new__() lors de la désérialisation : l'appel sera classe.__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__()

Des personnalisations plus poussées de la sérialisation sont possibles à l'aide de la méthode __getstate__(). Lorsque __getstate__() est définie, l'objet qu'elle renvoie (état de l'instance) est sérialisé en lieu et place du __dict__, le dictionnaire des attributs de l'instance.

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éthode extend(). Ceci est principalement utile aux les classes héritant de list, mais peut aussi servir sur d'autres classes, la seule contrainte étant qu'elles implémentent append() et extend() 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 de dict, 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, le code :

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 private dispatch table. On the other hand, the code

copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)

modifies the global dispatch table shared by all users of the copyreg module.

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 as 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+4j],
    '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 ou AttributeError, 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 that persistent IDs in protocol 0 are delimited by the newline character. Therefore if any kind of newline characters occurs in persistent IDs, the resulting pickled data will become unreadable.