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

How can I pickle a nested class in python?

我有一个嵌套类:

1
2
3
4
5
6
7
class WidgetType(object):

    class FloatType(object):
        pass

    class TextType(object):
        pass

…以及一个引用嵌套类类型(不是它的实例)的项目,如下所示

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

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

PicklingError: Can't pickle

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


pickle模块正在尝试从模块中获取texttype类。但由于类是嵌套的,所以它不起作用。詹森的建议会奏效的。pickle.py中的行负责错误消息:

1
2
3
4
5
6
7
8
    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)不会在嵌套类的情况下工作。要演示正在进行的操作,请尝试在酸洗实例之前添加这些行:

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

此代码将textType作为属性添加到模块中。腌制应该可以。不过我不建议你使用这个黑客。


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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
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__方法中的适当参数:

1
2
3
4
5
6
7
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__文档,这相对简单)。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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.


如果您使用dill而不是pickle,它会起作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> 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'>

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


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


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


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

使用嵌套类的唯一原因是将工作紧密的类封装在一起,对于我来说,您的特定示例看起来像是直接继承的候选者—将WidgetType类嵌套在一起没有好处;将它们放入模块中,而从基部WidgetType继承。