如何在python中pickle嵌套类?

2024-09-27 07:23:56 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个嵌套类:

class WidgetType(object):

    class FloatType(object):
        pass

    class TextType(object):
        pass

。。以及引用嵌套类类型(不是其实例)的项目

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

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

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

有没有办法在python中pickle嵌套类?


Tags: 项目self类型序列化objectinitdeftype
3条回答

我知道这是一个非常古老的问题,但是我从来没有看到过对这个问题的满意的解决方案,除了对代码进行重新构造的明显且最有可能是正确的答案。

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

^{} function的python文档声明您可以返回

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.

因此,您只需要一个对象,它可以返回适当类的实例。这个类必须本身是可选择的(因此,必须存在于__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__文档,这相对简单)。

同样的技术(稍加代码修改)可以应用于深度嵌套的类。

一个充分发挥作用的例子:

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

我最后要说的是记住其他答案的意思:

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

pickle模块正在尝试从该模块获取TextType类。但是由于类是嵌套的,所以它不起作用。贾森的建议会奏效的。 下面是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作为属性添加到模块中。腌制应该很好。不过,我不建议你用这种方法。

如果您使用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

相关问题 更多 >

    热门问题