Python 为什么pickle\uuuu getstate\uuuu首先接受pickle所需的实例作为返回值?
我想问“如何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
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])