anidadas - listas dentro de listas python
¿Cómo puedo recuperar una clase anidada en Python? (6)
Tengo una clase anidada:
class WidgetType(object): class FloatType(object): pass class TextType(object): pass
.. y un objeto que refiere el tipo de clase anidada (no una instancia de este) como este
class ObjectToPickle(object): def __init__(self): self.type = WidgetType.TextType
Intentar serializar una instancia de la clase ObjectToPickle da como resultado:
PicklingError: no se puede extraer <clase ''setmanager.app.site.widget_data_types.TextType''>
¿Hay alguna forma de saltear clases anidadas en python?
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 responsables 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 funcionará en el caso de clase anidado, por supuesto. Para demostrar lo que sucede intente agregar estas líneas antes de develar la instancia:
import sys
setattr(sys.modules[__name__], ''TextType'', WidgetType.TextType)
Este código agrega TextType como un atributo al módulo. El decapado debería funcionar bien. Sin embargo, no te aconsejo que uses este truco.
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.
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 clases que trabajen juntas, su ejemplo específico parece ser una candidata de herencia inmediata para mí; no hay beneficio en anidar clases WidgetType
juntas; ponerlos en un módulo y heredar de la base WidgetType
en WidgetType
lugar.
Pickle 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
.
Sé que esta es una pregunta muy antigua, pero nunca vi explícitamente una solución satisfactoria a esta pregunta que no sea la respuesta obvia y, con toda probabilidad, correcta para reestructurar el código.
Desafortunadamente, no siempre es práctico hacer tal cosa, en cuyo caso, como último recurso, es posible seleccionar instancias de clases que se definen dentro de otra clase.
La documentación de Python para la función __reduce__
indica que puede devolver
Un objeto invocable que se llamará para crear la versión inicial del objeto. El siguiente elemento de la tupla proporcionará argumentos para este invocable.
Por lo tanto, todo lo que necesita es un objeto que pueda devolver una instancia de la clase apropiada. Esta clase debe ser seleccionable (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 está anidada pero las instancias se pueden escanear (se necesita más trabajo para volcar / cargar la información __state__
, pero esto es relativamente sencillo según la documentación __reduce__
).
Esta misma técnica (con ligeras modificaciones de código) se puede aplicar para clases profundamente anidadas.
Un ejemplo completamente trabajado:
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 recordar lo que han dicho las otras respuestas:
Si está en condiciones de hacerlo, considere volver a factorizar su código para evitar las clases anidadas en primer lugar.
Si usa dill
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''>
Obtenga eneldo aquí: https://github.com/uqfoundation/dill