如何在 python 中腌制嵌套类?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1947904/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 23:25:20  来源:igfitidea点击:

How can I pickle a nested class in python?

pythonclassnestedpickle

提问by prinzdezibel

I have a nested class:

我有一个嵌套类:

class WidgetType(object):

    class FloatType(object):
        pass

    class TextType(object):
        pass

.. and an oject 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:

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

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

PicklingError:无法腌制 <class 'setmanager.app.site.widget_data_types.TextType'>

Is there a way to pickle nested classes in python?

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

回答by Nadia Alramli

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:

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

klass = getattr(mod, name)当然在嵌套类的情况下不起作用。为了演示正在发生的事情,尝试在酸洗实例之前添加这些行:

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.

此代码将 TextType 作为属性添加到模块。酸洗应该可以正常工作。不过,我不建议您使用此 hack。

回答by pelson

I know this is a veryold 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 ispossible to pickle instances of classes which are defined inside another class.

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

The python documentation for the __reduce__functionstates that you can return

__reduce__函数的 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.

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

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

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

All that is left therefore, is to return the appropriate arguments in a __reduce__method on FloatType:

因此,剩下的就是__reduce__在 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).

结果是一个嵌套的类,但实例可以被腌制(需要进一步的工作来转储/加载__state__信息,但根据__reduce__文档,这相对简单)。

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.

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

回答by Mike McKerns

If you use dillinstead of pickle, it works.

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

Get dill here: https://github.com/uqfoundation/dill

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

回答by Hivert

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 outerclass 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.

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

回答by Jason S

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 TextTypeand FloatTypein your code. Or, import the module they're in and use widget_type.TextTypeand widget_type.FloatType.

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

回答by kibitzer

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 WidgetTypesinstead of nested classes?

Nadia 的回答非常完整——这实际上不是你想做的事情;你确定你不能使用继承WidgetTypes而不是嵌套类吗?

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 WidgetTypeclasses together; put them in a module and inherit from the base WidgetTypeinstead.

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