"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"
-----------------------

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()" 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.
