如何在Python中对动态创建的嵌套类进行序列化?

42 投票
7 回答
27036 浏览
提问于 2025-04-15 17:17

我有一个嵌套类:

class WidgetType(object):
    
    class FloatType(object):
        pass
    
    class TextType(object):
        pass

.. 还有一个对象,它指向这个嵌套类的类型(不是它的实例),像这样:

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

尝试对ObjectToPickle类的一个实例进行序列化时,出现了:

PicklingError: 无法序列化 <class 'setmanager.app.site.widget_data_types.TextType'>

有没有办法在Python中序列化嵌套类?

7 个回答

6

如果你用 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'>

你可以在这里获取 dillhttps://github.com/uqfoundation/dill

30

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作为一个属性添加到模块中。这样序列化应该就能正常工作了。不过我不建议你使用这个方法。

35

我知道这个问题已经很老了,但我从来没有看到过一个令人满意的解决办法,除了显而易见的、很可能正确的答案,就是重构你的代码。

可惜,有时候这样做并不实际。在这种情况下,作为最后的手段,确实可以对定义在另一个类中的类的实例进行“序列化”。

Python的文档中提到的__reduce__函数说明,你可以返回

一个可调用的对象,这个对象会被调用来创建对象的初始版本。元组的下一个元素将提供这个可调用对象的参数。

所以,你只需要一个能够返回适当类实例的对象。这个类必须是可以序列化的(因此,必须在__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__, ))

这样就得到了一个嵌套的类,但其实例可以被序列化(还需要进一步处理以保存/加载__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

最后我想提醒大家,记住其他回答提到的:

如果你有条件的话,考虑重构你的代码,尽量避免使用嵌套类。

撰写回答