Python 为什么pickle\uuuu getstate\uuuu首先接受pickle所需的实例作为返回值?

Python 为什么pickle\uuuu getstate\uuuu首先接受pickle所需的实例作为返回值?,python,pickle,Python,Pickle,我想问“如何pickle一个继承自dict并定义\uuuuu插槽的类”。然后我意识到下面的B类中完全令人心碎的解决方案实际上是有效的 import pickle class A(dict): __slots__ = ["porridge"] def __init__(self, porridge): self.porridge = porridge class B(A): __slots__ = ["porridge"] def __getstate__(se

我想问“如何pickle一个继承自
dict
并定义
\uuuuu插槽的类”。然后我意识到下面的
B类
中完全令人心碎的解决方案实际上是有效的

import pickle

class A(dict):
    __slots__ = ["porridge"]
    def __init__(self, porridge): self.porridge = porridge

class B(A):
    __slots__ = ["porridge"]
    def __getstate__(self):
        # Returning the very item being pickled in 'self'??
        return self, self.porridge 
    def __setstate__(self, state):
        print "__setstate__(%s) type(%s, %s)" % (state, type(state[0]), 
                                                type(state[1]))
        self.update(state[0])
        self.porridge = state[1]
以下是一些输出:

>>> saved = pickle.dumps(A(10))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
>>> b = B('delicious')
>>> b['butter'] = 'yes please'
>>> loaded = pickle.loads(pickle.dumps(b))
__setstate__(({'butter': 'yes please'}, 'delicious')) type(<class '__main__.B'>, <type 'str'>)
>>> b
{'butter': 'yes please'}
>>> b.porridge
'delicious'
保存=pickle.dumps(A(10)) TypeError:定义了_插槽_而没有定义_getstate _的类不能被pickle >>>b=b(‘美味’) >>>b['butter']='yes please' >>>加载=pickle.load(pickle.dumps(b)) __设置状态({'butter':'yes please'},'delicious'))类型(,) >>>b {'butter':'yes please'} >>>粥 “美味”
因此,基本上,
pickle
无法在不定义
\uu getstate\uu
的情况下对定义了
\uuuuuuuuuu插槽的类进行pickle。如果类继承自
dict
,这是一个问题,因为如何在不返回
self
的情况下返回实例的内容,而
self
正是pickle正在尝试pickle的实例,如果不调用
\uu getstate\uuuuu
就无法执行此操作。注意
\uuuu setstate\uuuu
实际上是如何接收实例
B
作为状态的一部分的


好吧,它起作用了。。。但是有人能解释为什么吗?这是一个特性还是一个bug?

我的理解是这样的。如果您的类使用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
,这是一种确保没有任何意外属性的方法。与常规Python对象不同,使用插槽实现的对象不能动态添加属性

当Python使用
\uuuuu slots\uuuu
取消序列化对象时,它不希望仅仅假设序列化版本中的任何属性都与您的运行时类兼容。因此,它将这个问题推给您,您可以实现
\uuu getstate\uuuuuu
\uuuu setstate\uuuuu

但是,您实现
\uuu getstate\uuuu
\uuuuu setstate\uuuuu
的方式似乎在规避这一检查。下面是引发该异常的代码:

try:
    getstate = self.__getstate__
except AttributeError:
    if getattr(self, "__slots__", None):
        raise TypeError("a class that defines __slots__ without "
                        "defining __getstate__ cannot be pickled")
    try:
        dict = self.__dict__
    except AttributeError:
        dict = None
else:
    dict = getstate()
反过来说,您告诉Pickle模块将其异议放在一边,并像往常一样序列化和取消序列化对象

这可能是个好主意,也可能不是——我不确定。但我认为,如果您更改了类定义,然后使用与运行时类所期望的不同的属性集取消序列化对象,那么这种情况可能会再次影响您

这就是为什么,特别是在使用插槽时,您的
\uuu getstate\uuu
\uu getstate\uuuu
应该更加明确。我将明确指出,您只是在来回发送字典键/值,如下所示:

class B(A):
    __slots__ = ["porridge"]
    def __getstate__(self):
        return dict(self), self.porridge 
    def __setstate__(self, state):
        self.update(state[0])
        self.porridge = state[1]

注意
dict(self)
——它将对象强制转换为dict,这应该确保state元组中的第一个元素只是字典数据。

也许我参加聚会有点晚了,但这个问题没有得到一个真正解释发生了什么的答案,所以我们开始吧

对于那些不想阅读整篇文章(文章有点长…)的人,这里有一个简短的总结:

  • 您不需要处理
    \uu getstate\uuuu()中包含的
    dict
    实例--
    pickle
    将为您完成此操作

  • 如果仍然在状态中包含
    self
    pickle
    的循环检测将阻止无限循环

  • 为从
    dict派生的自定义类编写
    \uuuuu getstate\uuuuuuuuuuuuuuuuuuuuuuuuu()和
    方法
    让我们从编写类的
    \uu getstate\uu()
    \uu setstate\uu()
    方法的正确方法开始。您不需要处理
    B
    实例中包含的
    dict
    实例的内容--
    pickle
    知道如何处理字典,并将为您完成这项工作。因此,这种实施就足够了:

    class B(A):
        __slots__ = ["porridge"]
        def __getstate__(self):
            return self.porridge 
        def __setstate__(self, state):
            self.porridge = state
    
    例如:

    >>> a = B("oats")
    >>> a[42] = "answer"
    >>> b = pickle.loads(pickle.dumps(a))
    >>> b
    {42: 'answer'}
    >>> b.porridge
    'oats'
    
    您的实现中发生了什么? 为什么您的实现也能工作,以及在幕后发生了什么?这有点复杂,但是——一旦我们知道字典被修改了——就不难理解了。如果
    pickle
    模块遇到一个用户定义类的实例,它将调用该类的
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。让我们像最初一样再次定义
    B
    ,即使用
    的“递归”定义
    \uu getstate\uuuuu()
    ,现在让我们看看调用
    \uu reduce\uuuuu()
    的实例时得到了什么:

    >>> a = B("oats")
    >>> a[42] = "answer"
    >>> a.__reduce__()
    (<function _reconstructor at 0xb7478454>,
     (<class '__main__.B'>, <type 'dict'>, {42: 'answer'}),
     ({42: 'answer'}, 'oats'))
    
    到您的
    \uuuu设置状态()
    实现(它打印
    True
    !)。线路

    self.update(state[0])
    

    因此,只需更新实例本身。

    这是否允许存储循环引用?
    dict(self)
    不会将对象强制转换为dict。它会复制它。而且在性能上有着巨大的差异。除此之外,我当然觉得你的回答很有启发性。但它有点犹豫不决。我真的不需要答案,因为这只是一个实验,但我想知道这里到底发生了什么。也许我以后会赏金的。是的,它会创建一个散列的副本,如果你的字典很大或者你经常这样做,你就要付出代价。我有点犹豫不决,因为不清楚你到底做错了什么。。。您可以在Python邮件列表中询问。
    self.update(state[0])