我打算问"如何挑选一个继承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]
Run Code Online (Sandbox Code Playgroud)
这是一些输出:
>>> 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'
Run Code Online (Sandbox Code Playgroud)
所以基本上,pickle不能腌制定义的类__slots__而不定义__getstate__.如果类是继承的,那么这是一个问题dict- 因为如何在不返回的情况下返回实例的内容self,这是pickle已经尝试pickle的实例,并且在没有调用的情况下不能这样做__getstate__.请注意如何__setstate__实际接收实例B作为状态的一部分.
嗯,它有效...但有人能解释为什么吗?它是一个功能还是一个bug?
Sve*_*ach 16
也许我参加派对有点晚了,但是这个问题并没有得到一个真正解释发生了什么的答案,所以我们走了.
这里有一个快速摘要,为那些不想阅读这整篇文章的人(它有点长...):
您不需要处理所包含的dict实例__getstate__()- pickle将为您执行此操作.
self无论如何,如果你包含在状态中,pickle循环检测将阻止无限循环.
__getstate__()和__setstate__()方法派生自dict让我们先从写的正确方法__getstate__()和__setstate__()类的方法.您不需要处理dict实例中包含的B实例的内容- pickle知道如何处理字典并将为您执行此操作.所以这个实现就足够了:
class B(A):
__slots__ = ["porridge"]
def __getstate__(self):
return self.porridge
def __setstate__(self, state):
self.porridge = state
Run Code Online (Sandbox Code Playgroud)
例:
>>> a = B("oats")
>>> a[42] = "answer"
>>> b = pickle.loads(pickle.dumps(a))
>>> b
{42: 'answer'}
>>> b.porridge
'oats'
Run Code Online (Sandbox Code Playgroud)
为什么您的实施也能正常运行,以及在幕后发生了什么?这有点复杂,但是 - 一旦我们知道字典被腌制了 - 不是很难弄明白.如果pickle模块遇到用户定义类的实例,它会调用__reduce__()此类__getstate__()的__reduce_ex__()方法,而该方法又调用(实际上,它通常调用方法,但这并不重要).让我们B再次定义你最初的定义,即使用"recurisve"定义__getstate__(),让我们看看在调用now __reduce__()的实例时我们得到了什么B:
>>> a = B("oats")
>>> a[42] = "answer"
>>> a.__reduce__()
(<function _reconstructor at 0xb7478454>,
(<class '__main__.B'>, <type 'dict'>, {42: 'answer'}),
({42: 'answer'}, 'oats'))
Run Code Online (Sandbox Code Playgroud)
正如我们从文档中__reduce__()看到的那样,该方法返回一个2到5个元素的元组.第一个元素是一个函数,它将被调用以在unpickling时重构实例,第二个元素是将传递给此函数的参数元组,第三个元素是返回值__getstate__().我们已经可以看到字典信息被包含两次.该函数_reconstructor()是copy_reg模块的内部函数,它__setstate__()在unpickling调用之前重建基类.(如果你愿意的话,看一下这个函数的源代码 - 它很简短!)
现在,pickler需要腌制返回值a.__reduce__().它基本上一个接一个地腌制这个元组的三个元素.第二个元素是一个元组,它的项目也是一个接一个地腌制.这个内部元组(即a.__reduce__()[1][2])的第三项是类型,dict并使用内部pickler for the pickaries进行pickle.外元组(即a.__reduce__()[2])的第三个元素也是元组,由B实例本身和字符串组成.当对B实例进行pickle时,模块的循环检测会pickle启动: pickle实现已经处理过的这个确切的实例,并且只存储对它的引用id()而不是真正的pickle - 这就是为什么没有infinte循环发生的原因.
当再次解开这个混乱时,unpickler首先从流中读取重建函数及其参数.调用该函数,导致B已经初始化字典部分的实例.接下来,unpickler读取状态.它遇到一个元组,该元组由对已经未打开的对象的引用组成 - 即我们的实例B- 和一个字符串"oats".这个元组现在传递给了B.__setstate__().现在是第一个元素,state并且self是相同的对象,可以通过添加该行来看到
print self is state[0]
Run Code Online (Sandbox Code Playgroud)
你的__setstate__()实现(它打印True!).这条线
self.update(state[0])
Run Code Online (Sandbox Code Playgroud)
因此只需用自身更新实例.