我怎样才能在python中挑选一个嵌套类?

时间:2009-12-22 17:25:37

标签: python class nested pickle

我有一个嵌套类:

class WidgetType(object):

    class FloatType(object):
        pass

    class TextType(object):
        pass

..和一个引用嵌套类类型(不是它的实例)的对象,如此

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

尝试序列化ObjectToPickle类的实例会导致:

  

PicklingError:不能pickle< class   'setmanager.app.site.widget_data_types.TextType' >

有没有办法在python中腌制嵌套类?

6 个答案:

答案 0 :(得分:29)

pickle模块正试图从模块中获取TextType类。但由于该类是嵌套的,因此不起作用。 jasonjs的建议会奏效。 以下是pickle.py中负责错误消息的行:

    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)在嵌套类的情况下不起作用。为了演示正在发生的事情,尝试在挑选实例之前添加这些行:

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

此代码将TextType作为属性添加到模块中。酸洗应该工作得很好。我不建议你使用这个黑客。

答案 1 :(得分:28)

我知道这是一个非常旧问题,但我从未明确地看到过这个问题的满意解决方案,除了重新构建代码的明显且最可能正确的答案。 / p>

不幸的是,做这样的事情并不总是切实可行的,在这种情况下作为最后的手段, 可以挑选在另一个类中定义的类的实例。

您可以返回的__reduce__ function状态的python文档

  

将调用可调用对象以创建对象的初始版本。元组的下一个元素将为此可调用函数提供参数。

因此,您只需要一个可以返回相应类的实例的对象。此类必须本身可以选择(因此,必须生活在__main__级别),并且可以简单如下:

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

因此,剩下的就是在FloatType上的__reduce__方法中返回适当的参数:

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__, ))

结果是一个嵌套的类但是可以对实例进行pickle(转发/加载__state__信息需要进一步的工作,但根据__reduce__文档,这是相对简单的。)< / p>

这种相同的技术(略有代码修改)可以应用于深度嵌套的类。

一个完全有效的例子:

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

我对此的最后一点是要记住其他答案所说的内容:

  

如果您有能力这样做,请考虑将代码重新分解为   首先避免使用嵌套类。

答案 2 :(得分:5)

在Sage(www.sagemath.org)中,我们有很多这种酸洗问题。我们决定系统地解决它的方法是将外部类放在特定的元类中,其目标是实现和隐藏黑客。请注意,如果存在多个嵌套级别,则会自动通过嵌套类传播。

答案 3 :(得分:5)

如果您使用dill代替pickle,则可以使用。

>>> 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'>

在这里获取莳萝:https://github.com/uqfoundation/dill

答案 4 :(得分:2)

Pickle仅适用于模块范围(顶级)中定义的类。在这种情况下,看起来你可以在模块范围内定义嵌套类,然后在WidgetType上将它们设置为属性,假设有理由不在代码中引用TextTypeFloatType。或者,导入他们所在的模块并使用widget_type.TextTypewidget_type.FloatType

答案 5 :(得分:1)

Nadia的答案非常完整 - 实际上并不是你想要做的事情;你确定你不能在WidgetTypes而不是嵌套类中使用继承吗?

使用嵌套类的唯一原因是封装紧密协作的类,您的具体示例看起来像是我的直接继承候选者 - 将WidgetType类嵌套在一起没有任何好处;将它们放在一个模块中,然后从基础WidgetType继承。