为什么在尝试序列化对象时我会收到关于类定义__slots__的错误?
我正在尝试把我定义的一个(新式)类的对象进行序列化,但遇到了以下错误:
>>> with open('temp/connection.pickle','w') as f:
... pickle.dump(c,f)
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "/usr/lib/python2.5/pickle.py", line 1362, in dump
Pickler(file, protocol).dump(obj)
File "/usr/lib/python2.5/pickle.py", line 224, in dump
self.save(obj)
File "/usr/lib/python2.5/pickle.py", line 331, in save
self.save_reduce(obj=obj, *rv)
File "/usr/lib/python2.5/pickle.py", line 419, in save_reduce
save(state)
File "/usr/lib/python2.5/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/lib/python2.5/pickle.py", line 649, in save_dict
self._batch_setitems(obj.iteritems())
File "/usr/lib/python2.5/pickle.py", line 663, in _batch_setitems
save(v)
File "/usr/lib/python2.5/pickle.py", line 306, in save
rv = reduce(self.proto)
File "/usr/lib/python2.5/copy_reg.py", line 76, in _reduce_ex
raise TypeError("a class that defines __slots__ without "
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
我在我的类中没有明确地定义 __slots__
。那我做的某些事情是不是隐含地定义了它?我该怎么解决这个问题?我需要定义 __getstate__
吗?
更新: gnibbler 提供了一个很好的例子。我尝试序列化的对象的类是一个包装了套接字的类。(我现在意识到)套接字确实定义了 __slots__
,而不是 __getstate__
,这是有原因的。我猜想一旦一个进程结束,另一个进程就无法反序列化并使用之前进程的套接字连接。因此,虽然我接受了 Alex Martelli 的优秀回答,但我将不得不寻找一种不同于序列化的策略来“共享”对象引用。
3 个回答
来自 PEP 307:
__getstate__
方法应该返回一个可以被“打包”的值,这个值代表了对象的状态,但不直接引用这个对象本身。如果没有__getstate__
方法,系统会使用一个默认的实现,这个实现返回self.__dict__
。
可能你的实例有一个属性使用了 __slots__
。
比如说,socket
就有 __slots__
,所以它不能被序列化。
你需要找出哪个属性导致了这个错误,然后自己写 __getstate__
和 __setstate__
来忽略那个属性。
定义 __slots__
的类(而不是 __getstate__
)可以是你的祖先类,或者是你某个属性或项目的类(或祖先类),无论是直接还是间接的。简单来说,就是任何在以你的对象为根的有向图中引用的对象的类,因为在进行序列化时需要保存整个图。
解决你困扰的一个简单方法是使用协议 -1
,这意味着“pickle可以使用的最佳协议”;默认的协议是一个古老的基于ASCII的协议,这个协议对 __slots__
和 __getstate__
有一些限制。考虑以下代码:
>>> class sic(object):
... __slots__ = 'a', 'b'
...
>>> import pickle
>>> pickle.dumps(sic(), -1)
'\x80\x02c__main__\nsic\nq\x00)\x81q\x01.'
>>> pickle.dumps(sic())
Traceback (most recent call last):
[snip snip]
raise TypeError("a class that defines __slots__ without "
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
>>>
如你所见,协议 -1
能够很好地处理 __slots__
,而默认协议则会抛出你看到的那个异常。
使用协议 -1
的问题是:它生成的是二进制字符串/文件,而不是像默认协议那样的ASCII格式;因此,生成的序列化文件在一些较老版本的Python中无法加载。除了在 __slots__
方面的主要优势外,它还可以产生更紧凑的结果,并且性能更好。
如果你被迫使用默认协议,那么你需要准确找出是哪个类让你遇到麻烦,以及具体原因。如果是这种情况,我们可以讨论一些策略(但如果你能使用 -1
协议,那就太好了,根本不值得讨论;-) 如果简单的代码检查找出问题类/对象太复杂,我可以想一些基于深拷贝的技巧,帮助你获取整个图的可用表示,假如你对此感兴趣的话)。