2009-12-22 6 views
30

J'ai une classe imbriquée:Comment puis-je capturer une classe imbriquée en python?

 
class WidgetType(object): 

    class FloatType(object): 
     pass 

    class TextType(object): 
     pass 

.. et un ojet qui fait référence le type de classe imbriquée (pas un exemple de celui-ci) comme celui-ci

 
class ObjectToPickle(object): 
    def __init__(self): 
     self.type = WidgetType.TextType 

de sérialisation une instance de la ObjectToPickle les résultats de la classe dans:

PicklingError: Can't pickle <class 'setmanager.app.site.widget_data_types.TextType'>

est-il un moyen de décaper classes imbriquées en python?

+1

hehe, est votre nom d'utilisateur une coïncidence? : p –

+0

Voir aussi la documentation Python pickle: http://docs.python.org/library/pickle.html#the-pickle-protocol – joeforker

+0

En Python 3.4 et supérieur, 'inst = ObjectToPickle(); pickle.dumps (inst, 4) 'fonctionne bien. – Eponymous

Répondre

1

Pickle ne fonctionne qu'avec les classes définies dans la portée du module (niveau supérieur). Dans ce cas, il semble que vous puissiez définir les classes imbriquées dans la portée du module, puis les définir en tant que propriétés sur WidgetType, en supposant qu'il existe une raison de ne pas simplement référencer TextType et FloatType dans votre code. Ou, importez le module dans lequel ils se trouvent et utilisez widget_type.TextType et widget_type.FloatType.

+0

[..] les définir en tant que propriétés sur WidgetType [..] Cela permet d'obtenir: "TypeError: impossible de capturer des objets de propriété" – prinzdezibel

24

Le module pickle tente d'obtenir la classe TextType à partir du module. Mais puisque la classe est imbriquée, cela ne fonctionne pas. La suggestion de jasonjs fonctionnera. Voici les lignes pickle.py responsable du message d'erreur:

try: 
     __import__(module) 
     mod = sys.modules[module] 
     klass = getattr(mod, name) 
    except (ImportError, KeyError, AttributeError): 
     raise PicklingError(
      "Can't pickle %r: it's not found as %s.%s" % 
      (obj, module, name)) 

klass = getattr(mod, name) ne fonctionnera pas dans le cas de classe imbriquée bien sûr. Pour démontrer ce qui se passe essayer d'ajouter ces lignes avant le décapage de l'instance:

import sys 
setattr(sys.modules[__name__], 'TextType', WidgetType.TextType) 

Ce code ajoute TextType comme un attribut au module. Le décapage devrait fonctionner très bien. Je ne vous conseille pas d'utiliser ce hack si.

+0

A +. Ran dans cela, et il semble que le cornichon est juste mauvais à utiliser ces jours-ci. Aussi trouvé votre barre de progression du terminal, qui est vraiment soignée! – Kumba

+2

Il est légèrement plus simple d'aliaser le nom de la classe interne à la fin du module avec 'TextType = WidgetType.TextType', bien qu'il ne le rende pas moins hacky et qu'il crie toujours" supprimer la classe interne ". – Amoss

1

La réponse de Nadia est plutôt complète - ce n'est pratiquement pas quelque chose que vous voulez faire; Êtes-vous sûr de ne pas pouvoir utiliser l'héritage dans WidgetTypes au lieu de classes imbriquées? La seule raison d'utiliser des classes imbriquées est d'encapsuler des classes travaillant en étroite collaboration. Votre exemple spécifique ressemble à un héritage immédiat. Il n'y a aucun avantage à imbriquer des classes WidgetType ensemble; mettez-les dans un module et héritez de la base WidgetType à la place.

+1

Je suis tombé dessus parce que j'utilisais 'cPickle' avec' hashlib.sha1() 'pour obtenir un hachage d'une instance d'objet donnée. À l'aide d'un module tiers, [dpkt] (http://code.google.com/p/dpkt/), j'ai rencontré le besoin d'analyser les données ICMP à partir d'un fichier libpcap et découvert que dpkt utilisait des classes imbriquées à la fois ' ICMP() 'et' ICMP6() '. Changer le code dans icmp.py de dpkt fonctionne unorund ceci, mais ce n'est pas une solution pratique sur d'autres systèmes que je ne contrôle pas. Par conséquent, votre réponse, bien que raisonnable, ne s'appliquera pas à tous les cas en raison de la façon dont les autres vont écrire leur code. – Kumba

4

Dans Sage (www.sagemath.org), nous avons plusieurs cas de ce problème de décapage. La façon dont nous avons décidé de le résoudre systématiquement est de placer la classe externe à l'intérieur d'une métaclasse spécifique dont le but est d'implémenter et de cacher le hack. Notez que ceci se propage automatiquement à travers les classes imbriquées s'il y a plusieurs niveaux d'imbrication.

22

Je sais que c'est très vieille question, mais je n'ai jamais explicitement vu une solution satisfaisante à cette question autre que la réponse évidente, et le plus probablement correcte, de re-structurer votre code.

Malheureusement, ce n'est pas toujours pratique de faire une telle chose, dans ce cas, comme dernier recours, il est possible de décaper des instances de classes qui sont définies dans une autre classe.

La documentation python pour les __reduce__ function états que vous pouvez retourner

A callable object that will be called to create the initial version of the object. The next element of the tuple will provide arguments for this callable.

Par conséquent, tout ce que vous avez besoin est un objet qui peut renvoyer une instance de la classe appropriée. Cette classe doit être elle-même picklable (donc, doit vivre au niveau __main__), et pourrait être aussi simple que:

class _NestedClassGetter(object): 
    """ 
    When called with the containing class as the first argument, 
    and the name of the nested class as the second argument, 
    returns an instance of the nested class. 
    """ 
    def __call__(self, containing_class, class_name): 
     nested_class = getattr(containing_class, class_name) 
     # return an instance of a nested_class. Some more intelligence could be 
     # applied for class construction if necessary. 
     return nested_class() 

Tout ce qui reste, est donc de renvoyer les arguments appropriés dans une méthode __reduce__ sur FloatType:

class WidgetType(object): 

    class FloatType(object): 
     def __reduce__(self): 
      # return a class which can return this class when called with the 
      # appropriate tuple of arguments 
      return (_NestedClassGetter(), (WidgetType, self.__class__.__name__,)) 

le résultat est une classe qui est imbriqué, mais les instances peuvent être décapés (travaux supplémentaires sont nécessaires pour vider/charger les informations __state__, mais cela est relativement simple selon la documentation __reduce__).

Cette même technique (avec de légères modifications de code) peut être appliquée pour les classes profondément imbriquées.

Un exemple bien travaillé:

import pickle 


class ParentClass(object): 

    class NestedClass(object): 
     def __init__(self, var1): 
      self.var1 = var1 

     def __reduce__(self): 
      state = self.__dict__.copy() 
      return (_NestedClassGetter(), 
        (ParentClass, self.__class__.__name__,), 
        state, 
        ) 


class _NestedClassGetter(object): 
    """ 
    When called with the containing class as the first argument, 
    and the name of the nested class as the second argument, 
    returns an instance of the nested class. 
    """ 
    def __call__(self, containing_class, class_name): 
     nested_class = getattr(containing_class, class_name) 

     # make an instance of a simple object (this one will do), for which we can change the 
     # __class__ later on. 
     nested_instance = _NestedClassGetter() 

     # set the class of the instance, the __init__ will never be called on the class 
     # but the original state will be set later on by pickle. 
     nested_instance.__class__ = nested_class 
     return nested_instance 



if __name__ == '__main__': 

    orig = ParentClass.NestedClass(var1=['hello', 'world']) 

    pickle.dump(orig, open('simple.pickle', 'w')) 

    pickled = pickle.load(open('simple.pickle', 'r')) 

    print type(pickled) 
    print pickled.var1 

Ma note finale à ce sujet est de se rappeler ce que les autres réponses ont dit:

If you are in a position to do so, consider re-factoring your code to avoid the nested classes in the first place.

5

Si vous utilisez dill au lieu de pickle, cela fonctionne.

>>> import dill 
>>> 
>>> class WidgetType(object): 
... class FloatType(object): 
...  pass 
... class TextType(object): 
...  pass 
... 
>>> class ObjectToPickle(object): 
... def __init__(self): 
...  self.type = WidgetType.TextType 
... 
>>> x = ObjectToPickle() 
>>> 
>>> _x = dill.dumps(x) 
>>> x_ = dill.loads(_x) 
>>> x_ 
<__main__.ObjectToPickle object at 0x10b20a250> 
>>> x_.type 
<class '__main__.TextType'> 

Get aneth ici: https://github.com/uqfoundation/dill

+0

Je n'ai pas trouvé d'informations concernant la manipulation des objets de décapage par Dill à travers différents espaces de noms (voir [this] (http://stackoverflow.com/questions/26696695/store-object-using-python-pickle-and-load-it-into -different-namespace) question)? – Jens

+0

'dill' peut décaper la classe par référence ou par définition de classe (c'est-à-dire qu'elle peut décaper le code source de la classe). –

Questions connexes