如何在 python 中腌制一个动态创建的嵌套类?

Posted

技术标签:

【中文标题】如何在 python 中腌制一个动态创建的嵌套类?【英文标题】:How can I pickle a dynamically created nested class in python? 【发布时间】:2010-12-29 04:58:38 【问题描述】:

我有一个嵌套类:

类小部件类型(对象): 类浮动类型(对象): 经过 类文本类型(对象): 经过

.. 和一个像这样引用嵌套类类型(不是它的实例)的对象

类 ObjectToPickle(对象): def __init__(self): self.type = WidgetType.TextType

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

PicklingError: 不能腌制

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

【问题讨论】:

呵呵,你的用户名是巧合吗? :p 另见 Python pickle 文档:docs.python.org/library/pickle.html#the-pickle-protocol 在 Python 3.4 及更高版本中,inst=ObjectToPickle(); pickle.dumps(inst, 4) 工作正常。 【参考方案1】:

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

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

__reduce__ function 的 python 文档声明您可以返回

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

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

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

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

【讨论】:

要使其在 Python 3 中工作,请将 w 替换为 wb,并将 r 替换为 rb 为了记录,这对于动态生成的类(例如由函数返回的类)也很有效。尽管我们应该注意到,在这种情况下,单个 Python 进程中的类在技术上并不相同,除非我们根据参数缓存并重用生成的类。 看起来你可以只使用一个函数而不是一个完全无状态的可调用类的实例?【参考方案2】:

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

【讨论】:

A+。碰到这个,现在泡菜似乎不好用。还发现了你的终端进度条,真漂亮! TextType = WidgetType.TextType为模块末尾的内部类名取别名会稍微简单一些,尽管它并没有减少它的hacky并且它仍然尖叫着“删除内部类”。 【参考方案3】:

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

【讨论】:

我没有找到关于 Dill 处理不同命名空间中的酸洗对象的信息(请参阅this 问题)? dill 可以通过引用或类定义来腌制类(即它可以腌制类源代码)。【参考方案4】:

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

【讨论】:

更多详细说明或示例会有所帮助。【参考方案5】:

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

【讨论】:

[..]将它们设置为 WidgetType[..] 上的属性 这样做会产生:“TypeError: can't pickle property objects”【参考方案6】:

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

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

【讨论】:

我遇到了这个问题,因为我使用 cPicklehashlib.sha1() 来获取给定对象实例的哈希值。使用第三方模块dpkt,我遇到了从libpcap 文件解析ICMP 数据的需求,发现dpkt 在ICMP()ICMP6() 中都使用了嵌套类。更改 dpkt 的 icmp.py 中的代码可以解决这个问题,但这在我无法控制的其他系统上不是一个实用的解决方案。因此,您的答案虽然明智,但并不适用于所有情况,因为其他人将如何编写他们的代码。【参考方案7】:

这似乎在较新版本的 Python 中运行良好。我在 v3.8 中尝试过,它能够腌制和取消腌制嵌套类。

【讨论】:

以上是关于如何在 python 中腌制一个动态创建的嵌套类?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python 中最好地保存多个类实例?更新:如何腌制包含 osgeo.ogr 对象的类实例?

如何在python中动态生成嵌套for循环[重复]

如何在 Python 3 中腌制和取消腌制到可移植字符串

腌制具有 __slots__ 的冻结数据类

腌制一个类时,我在 python 中的行为与在 cython 中的行为不同

在python,pygame中腌制游戏数据