gpt4 book ai didi

python - 为什么pickle __getstate__ 接受它首先需要__getstate__ 进行pickle 的实例作为返回值?

转载 作者:太空狗 更新时间:2023-10-29 17:59:51 29 4
gpt4 key购买 nike

我想问“如何 pickle 一个继承自 dict 并定义 __slots__ 的类”。然后我在 class 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不能 pickle 定义 __slots__ 的类没有同时定义 __getstate__ .如果类继承自 dict,这是一个问题- 因为不返回 self怎么返回实例的内容,这是pickle 已经尝试pickle 的实例,如果不调用 __getstate__ 就不能这样做。 .注意如何 __setstate__实际上正在接收一个实例 B作为国家的一部分。

嗯,它有效......但有人可以解释为什么吗?这是一个功能还是一个错误?

最佳答案

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

对于那些不想阅读整篇文章的人来说,这是一个快速摘要(它有点长......):

  • 您不需要照顾所包含的 dict实例在 __getstate__() -- pickle会为你做这件事。
  • 如果您包括 self反正在状态,pickle的循环检测将防止无限循环。

  • 写作 __getstate__()__setstate__()派生自 dict 的自定义类的方法

    让我们从正确的方式开始写 __getstate__()__setstate__()你的类的方法。您无需处理 dict 的内容的 pickle 。实例包含在 B 中实例 -- 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 ——就不难弄清楚了。如果 pickle模块遇到用户定义类的实例,它调用 __reduce__()此类的方法,依次调用 __getstate__() (实际上,它通常会调用 __reduce_ex__() 方法,但这在这里无关紧要)。让我们定义 B再次像您最初所做的那样,即使用 __getstate__() 的“递归”定义,让我们看看调用 __reduce__() 会得到什么对于 B 的实例现在:
    >>> a = B("oats")
    >>> a[42] = "answer"
    >>> a.__reduce__()
    (<function _reconstructor at 0xb7478454>,
    (<class '__main__.B'>, <type 'dict'>, {42: 'answer'}),
    ({42: 'answer'}, 'oats'))

    the documentation of __reduce__() 可以看出,该方法返回一个包含 2 到 5 个元素的元组。第一个元素是一个函数,在 unpickling 时将被调用以重构实例,第二个元素是将传递给该函数的参数元组,第三个元素是 __getstate__() 的返回值。 .我们已经可以看到字典信息被包含了两次。函数 _reconstructor()copy_reg 的内部函数重建基类之前的模块 __setstate__()在 unpickling 时调用。 (如果您愿意,可以查看 source code of this function - 它很短!)

    现在pickler需要对 a.__reduce__()的返回值进行pickle .它基本上一个接一个地 pickle 这个元组的三个元素。第二个元素又是一个元组,它的元素也是一个接一个的pickle。此内部元组的第三项(即 a.__reduce__()[1][2] )的类型为 dict并使用字典的内部pickler 进行pickle。外部元组的第三个元素(即 a.__reduce__()[2] )也是一个元组,由 B 组成实例本身和一个字符串。 pickle 时 B例如, cycle detection of the pickle module开始: pickle意识到这个确切的实例已经被处理,并且只存储对其 id() 的引用。而不是真正 pickle 它——这就是为什么没有无限循环发生的原因。

    当再次解压这个烂摊子时,解压器首先从流中读取重构函数及其参数。该函数被调用,导致 B字典部分已经初始化的实例。接下来,unpickler 读取状态。它遇到了一个元组,它包含对一个已经解压的对象的引用——即我们的 B 实例。 -- 和一个字符串, "oats" .这个元组现在传递给 B.__setstate__() . state的第一个元素和 self现在是同一个对象,通过添加行可以看出
    print self is state[0]

    给您的 __setstate__()实现(它打印 True !)。线
    self.update(state[0])

    因此,只需简单地用自身更新实例。

    关于python - 为什么pickle __getstate__ 接受它首先需要__getstate__ 进行pickle 的实例作为返回值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5247250/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com