La référence pickle
indique que l'ensemble des objets pouvant être décapés est plutôt limité. En effet, j'ai une fonction qui retourne une classe générée de manière dinamique et j'ai constaté que je ne pouvais pas pickler les instances de cette classe:
>>> import pickle
>>> def f():
... class A: pass
... return A
...
>>> LocalA = f()
>>> la = LocalA()
>>> with open('testing.pickle', 'wb') as f:
... pickle.dump(la, f, pickle.HIGHEST_PROTOCOL)
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: Can't pickle local object 'f.<locals>.A'
De tels objets sont trop compliqués pour pickle
. D'accord. Maintenant, ce qui est magique, c'est que si j'essaie de décaper un objet similaire, mais d'une classe dérivée, cela fonctionne!
>>> class DerivedA(LocalA): pass
...
>>> da = DerivedA()
>>> with open('testing.pickle', 'wb') as f:
... pickle.dump(da, f, pickle.HIGHEST_PROTOCOL)
...
>>>
Qu'est-ce qu'il se passe ici? Si cela est si facile, pourquoi pickle
n'utilise-t-il pas cette solution de contournement pour implémenter une méthode dump
permettant de décaper les «objets locaux»?
Je pense que vous n'avez pas lu la référence que vous citez attentivement. La référence indique également clairement que seuls les objets suivants sont décapables:
- fonctions définies au niveau supérieur d'un module (en utilisant def, pas> lambda)
- fonctions intégrées définies au niveau supérieur d'un module
- classes définies au niveau supérieur d'un module
Votre exemple
>>> def f():
... class A: pass
... return A
ne définit pas une classe au niveau supérieur d'un module, il définit une classe dans la portée de f()
. pickle
fonctionne sur classes globales, pas des classes locales. Cela échoue automatiquement le test de picklable.
DerivedA
est une classe globale, donc tout va bien.
Pour ce qui est de savoir pourquoi seules les classes et les fonctions de niveau supérieur (globales selon vous) ne peuvent pas être décapées, la référence répond également à cette question (mine audacieuse):
Notez que les fonctions (intégrées et définies par l'utilisateur) sont traitées par "qualifié complet", référence de nom, et non par valeur. Cela signifie que seuls le nom de la fonction est traité, ainsi que le nom du module dans lequel la fonction est définie. Ni le code de la fonction, ni aucun de ses attributs ne sont traités. Ainsi, le module de définition doit pouvoir être importé dans l’environnement de dépickage et doit contenir l’objet nommé, sinon une exception sera déclenchée.
De la même manière, les classes sont décapées par référence nommée, de sorte que les mêmes restrictions s'appliquent dans l'environnement de décompression.
Donc là vous l'avez. pickle
ne sérialise les objets que par référence de nom, et non par les instructions brutes contenues dans l'objet. En effet, le travail pickle's
consiste à sérialiser hiérarchie d'objet} _, et rien d'autre.
Je ne suis pas d'accord, vous pouvez mariner les deux. Vous devez simplement utiliser un meilleur sérialiseur, tel que dill
. dill
(par défaut) sélectionne les classes en enregistrant la définition de la classe au lieu de pickling par référence, afin que votre premier cas n'échoue pas Vous pouvez même utiliser dill
pour obtenir le code source, si vous le souhaitez.
>>> import dill as pickle
>>> def f():
... class A: pass
... return A
...
>>> localA = f()
>>> la = localA()
>>>
>>> _la = pickle.dumps(la)
>>> la_ = pickle.loads(_la)
>>>
>>> class DerivedA(localA): pass
...
>>> da = DerivedA()
>>> _da = pickle.dumps(da)
>>> da_ = pickle.loads(_da)
>>>
>>> print(pickle.source.getsource(la_.__class__))
class A: pass
>>>
Les instances DerivedA
peuvent être sélectionnées car DerivedA
est disponible via une variable globale correspondant à son nom complet, qui correspond à la façon dont pickle
recherche les classes lors du dépickage.
Le problème en essayant de faire quelque chose comme ça avec les classes locales, c'est qu'il n'y a rien qui identifie auquel A
classe correspond à une instance. Si vous exécutez f
deux fois, vous obtenez deux classes A
et il est impossible de déterminer laquelle doit être la classe des instances A
unpickled d'une autre exécution du programme. Si vous n'exécutez pas du tout f
, vous obtenez les classes no A
, et que diable faites-vous du type d'instances non picklées?
Vous ne pouvez choisir que des instances de classes définies au niveau supérieur du module.
Cependant, vous pouvez choisir des instances de classes définies localement si vous les promouvez au plus haut niveau.
Vous devez définir l'attribut_ QUALNAME_class de la classe locale. Ensuite, vous devez affecter la classe à une variable de niveau supérieur du même nom.
def define_class(name):
class local_class:
pass
local_class.__qual= name
return local_class
class_A = define_class('class_A') # picklable
class_B = define_class('class_B') # picklable
class_X = define_class('class_Y') # unpicklable, names don't match