2009-12-22 8 views
30

I tienen una clase anidada:¿Cómo puedo recuperar una clase anidada en python?

 
class WidgetType(object): 

    class FloatType(object): 
     pass 

    class TextType(object): 
     pass 

.. y un oject que se refiere el tipo anidado clase (no es un ejemplo de la misma) como este

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

intentar serializar una instancia de la ObjectToPickle Resultados de clase en:

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

¿Hay alguna forma de saltear clases anidadas en python?

+1

jeje, es su nombre de usuario una coincidencia? : p –

+0

Vea también la documentación de picking de Python: http://docs.python.org/library/pickle.html#the-pickle-protocol – joeforker

+0

En Python 3.4 y superior, 'inst = ObjectToPickle(); pickle.dumps (inst, 4) 'funciona bien. – Eponymous

Respuesta

1

Salmuera solo funciona con las clases definidas en el alcance del módulo (nivel superior). En este caso, parece que podría definir las clases anidadas en el alcance del módulo y luego establecerlas como propiedades en WidgetType, suponiendo que haya una razón para no hacer referencia simplemente a TextType y FloatType en su código. O bien, importe el módulo en el que se encuentran y use widget_type.TextType y widget_type.FloatType.

+0

[..] configúrelos como propiedades en WidgetType [..] Realizando rendimientos: "TypeError: no se pueden salpicar objetos de propiedad" – prinzdezibel

24

El módulo pickle está tratando de obtener la clase TextType del módulo. Pero dado que la clase está anidada, no funciona. La sugerencia de jasonjs funcionará. Estas son las líneas en pickle.py responsable del mensaje de error:

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) no va a funcionar en el caso clase anidada por supuesto. Para demostrar lo que está pasando intenta agregar estas líneas antes de decapado de la instancia:

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

Este código añade TextType como un atributo al módulo. El decapado debería funcionar bien. Sin embargo, no te aconsejo que uses este truco.

+0

A +. Me encontré con esto, y parece que saltear es simplemente malo en estos días. ¡También encontré la barra de progreso de tu terminal, que está realmente ordenada! – Kumba

+2

Es un poco simple alias el nombre de la clase interna al final del módulo con 'TextType = WidgetType.TextType', aunque no lo hace menos hacky y todavía grita" eliminar la clase interna ". – Amoss

1

La respuesta de Nadia es bastante completa: prácticamente no es algo que quieras hacer; ¿Estás seguro de que no puedes usar la herencia en WidgetTypes en lugar de las clases anidadas?

La única razón para usar clases anidadas es encapsular las clases que trabajan juntas, su ejemplo específico parece ser un candidato de herencia inmediato para mí; no hay beneficio en anidar las clases WidgetType juntas; colóquelos en un módulo y herede de la base WidgetType en su lugar.

+1

Me topé con esto porque estaba usando 'cPickle' con' hashlib.sha1() 'para obtener un hash de una instancia de objeto dada. Usando un módulo de terceros, [dpkt] (http://code.google.com/p/dpkt/), me encontré con la necesidad de analizar los datos ICMP de un archivo libpcap y descubrí que dpkt usa clases anidadas en ambos ' ICMP() 'y' ICMP6() '. Cambiar el código en las obras icmp.py de dpkt significa esto, pero esta no es una solución práctica en otros sistemas que no controlo. Por lo tanto, su respuesta, aunque sensata, no se aplicará a todos los casos debido a la forma en que otras personas escribirán su código. – Kumba

4

En Sage (www.sagemath.org), tenemos muchas instancias de este problema de decapado. La forma en que decidimos resolverlo sistemáticamente es poner la clase externa dentro de una metaclase específica cuyo objetivo es implementar y ocultar el truco. Tenga en cuenta que esto se propaga automáticamente a través de clases anidadas si hay varios niveles de anidación.

22

Sé que esta es una pregunta muy antigua muy, pero nunca he visto explícitamente una solución satisfactoria a esta pregunta que no sea la respuesta obvia, y probablemente correcta, para volver a estructurar el código.

Por desgracia, no siempre es práctico para hacer tal cosa, en cuyo caso, como último recurso, que es posible conservar en vinagre instancias de clases que se definen dentro de otra clase.

La documentación de Python para la __reduce__ function estados que puede volver

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.

Por lo tanto, todo lo que necesita es un objeto que puede devolver una instancia de la clase apropiada. Esta clase debe ser en sí mismo estibables (por lo tanto, debe vivir en el nivel __main__), y podría ser tan simple como:

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

Todo lo que queda, por lo tanto, es devolver los argumentos apropiados en un método __reduce__ en 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__,)) 

el resultado es una clase que se anida pero instancias pueden ser en escabeche (se necesita más trabajo para volcar/cargar la información de __state__, pero esto es relativamente sencillo como por la documentación __reduce__).

Esta misma técnica (con ligeras modificaciones de código) se puede aplicar para las clases profundamente anidadas.

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 

Mi nota final sobre esto es para recordar lo que las otras respuestas han dicho::

Un ejemplo práctico totalmente

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 utiliza dill en lugar de pickle, funciona.

>>> 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 eneldo aquí: https://github.com/uqfoundation/dill

+0

No encontré información con respecto al manejo de Dill de los objetos de decapado en diferentes espacios de nombres (ver [esto] (http://stackoverflow.com/questions/26696695/store-object-using-python-pickle-and-load-it-into -different-namespace) pregunta)? – Jens

+0

'eneldo 'puede extraer la clase por referencia o por definición de clase (es decir, puede extraer el código fuente de la clase). –

Cuestiones relacionadas