如何在 python 中pickle动态创建的嵌套类?

发布于 2024-08-15 16:38:47 字数 503 浏览 8 评论 0 原文

我有一个嵌套类:

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

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

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

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

PicklingError:无法 pickle

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

I have a nested class:

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

.. and an object that refers the nested class type (not an instance of it) like this

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

Trying to serialize an instance of the ObjectToPickle class results in:

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

Is there a way to pickle nested classes in python?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

回忆躺在深渊里 2024-08-22 16:38:47

我知道这是一个非常的老问题,但除了重新构建代码的明显且最有可能正确的答案之外,我从未明确见过该问题的令人满意的解决方案。

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

__reduce__ 函数的 python 文档< /a> 表明您可以返回

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

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

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

如果您有能力这样做,请考虑重构您的代码以
首先避免嵌套类。

I know this is a very old question, but I have never explicitly seen a satisfactory solution to this question other than the obvious, and most likely correct, answer to re-structure your code.

Unfortunately, it is not always practical to do such a thing, in which case as a very last resort, it is possible to pickle instances of classes which are defined inside another class.

The python documentation for the __reduce__ function states that you can return

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.

Therefore, all you need is an object which can return an instance of the appropriate class. This class must itself be picklable (hence, must live on the __main__ level), and could be as simple as:

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

All that is left therefore, is to return the appropriate arguments in a __reduce__ method on 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__, ))

The result is a class which is nested but instances can be pickled (further work is needed to dump/load the __state__ information, but this is relatively straightforward as per the __reduce__ documentation).

This same technique (with slight code modifications) can be applied for deeply nested classes.

A fully worked example:

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

My final note on this is to remember what the other answers have said:

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

不可一世的女人 2024-08-22 16:38:47

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 作为属性添加到模块中。酸洗效果应该很好。不过我不建议你使用这个 hack。

The pickle module is trying to get the TextType class from the module. But since the class is nested it doesn't work. jasonjs's suggestion will work.
Here are the lines in pickle.py responsible for the error message:

    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) will not work in the nested class case of course. To demonstrate what is going on try to add these lines before pickling the instance:

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

This code adds TextType as an attribute to the module. The pickling should work just fine. I don't advice you to use this hack though.

旧伤慢歌 2024-08-22 16:38:47

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

If you use dill instead of pickle, it works.

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

春夜浅 2024-08-22 16:38:47

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

In Sage (www.sagemath.org), we have many instances of this pickling issue. The way we decided to systematically solve it is to put the outer class inside a specific metaclass whose goal is to implement and hide the hack. Note that this automatically propagate through nested classes if there are several level of nesting.

晚雾 2024-08-22 16:38:47

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

Pickle only works with classes defined in module scope (top level). In this case, it looks like you could define the nested classes in module scope and then set them as properties on WidgetType, assuming there's a reason not to just reference TextType and FloatType in your code. Or, import the module they're in and use widget_type.TextType and widget_type.FloatType.

恋竹姑娘 2024-08-22 16:38:47

纳迪亚的回答相当完整——这实际上不是你想做的事情;这是你想要做的事情。您确定不能在 WidgetTypes 中使用继承而不是嵌套类吗?

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

Nadia's answer is pretty complete - it is practically not something you want to be doing; are you sure you can't use inheritance in WidgetTypes instead of nested classes?

The only reason to use nested classes is to encapsulate classes working together closely, your specific example looks like an immediate inheritance candidate to me - there is no benefit in nesting WidgetType classes together; put them in a module and inherit from the base WidgetType instead.

注定孤独终老 2024-08-22 16:38:47

这似乎在较新版本的 Python 中工作得很好。我在 v3.8 中尝试过,它能够 pickle 和 unpickle 嵌套类。

This seems to work fine in newer versions of Python. I tried it in v3.8 and it was able to pickle and unpickle the nested class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文