为什么在尝试序列化对象时我会收到关于类定义__slots__的错误?

30 投票
3 回答
12663 浏览
提问于 2025-04-15 18:55

我正在尝试把我定义的一个(新式)类的对象进行序列化,但遇到了以下错误:

>>> 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 个回答

2

来自 PEP 307:

__getstate__ 方法应该返回一个可以被“打包”的值,这个值代表了对象的状态,但不直接引用这个对象本身。如果没有 __getstate__ 方法,系统会使用一个默认的实现,这个实现返回 self.__dict__

7

可能你的实例有一个属性使用了 __slots__

比如说,socket 就有 __slots__,所以它不能被序列化。

你需要找出哪个属性导致了这个错误,然后自己写 __getstate____setstate__ 来忽略那个属性。

32

定义 __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 协议,那就太好了,根本不值得讨论;-) 如果简单的代码检查找出问题类/对象太复杂,我可以想一些基于深拷贝的技巧,帮助你获取整个图的可用表示,假如你对此感兴趣的话)。

撰写回答