web-dev-qa-db-fra.com

Je peux "décaper des objets locaux" si j'utilise une classe dérivée?

La référence pickleindique 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»?

16
fonini

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.

24
Akshat Mahajan

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

>>> 
14
Mike McKerns

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?

2
user2357112

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
1
haael