腌制具有__slots__的冻结数据类

时间:2019-03-22 19:59:36

标签: python pickle slots python-dataclasses

如何用__slots__腌制冻结数据类的实例?例如,以下代码在Python 3.7.0中引发异常:

import pickle
from dataclasses import dataclass

@dataclass(frozen=True)
class A:
  __slots__ = ('a',)
  a: int

b = pickle.dumps(A(5))
pickle.loads(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'a'

如果我删除了frozen__slots__,这将起作用。这只是一个错误吗?

1 个答案:

答案 0 :(得分:3)

问题来自于pickle在设置插槽状态时使用实例的__setattr__方法。

默认__setsate___pickle.c line 6220load_build中定义。

对于状态dict中的项目,实例__dict__被直接更新:

 if (PyObject_SetItem(dict, d_key, d_value) < 0)

对于slotstate字典中的项目,使用实例的__setattr__

if (PyObject_SetAttr(inst, d_key, d_value) < 0)

现在,由于实例已冻结,因此__setattr__在加载时会引发FrozenInstanceError

要避免这种情况,您可以定义自己的__setstate__方法,该方法将使用object.__setattr__,而不是实例的__setattr__

docs为此发出了某种警告:

  

使用Frozen = True时,性能损失很小:__init__()不能使用简单的赋值来初始化字段,而必须使用object.__setattr__()

最好将__getstate__定义为__dict__的实例在您的情况下始终为None。如果不这样做,则state的{​​{1}}参数将是一个元组__setstate__,第一个值是实例的(None, {'a': 5})的值,第二个是slotstate字典。

__dict__

我个人不会将其称为bug,因为酸洗过程的设计是灵活的,但是功能增强仍有空间。酸洗协议的修订版可以在将来解决此问题。除非我遗漏了一些东西,并且除了微小的性能损失,否则对所有广告位使用import pickle from dataclasses import dataclass @dataclass(frozen=True) class A: __slots__ = ('a',) a: int def __getstate__(self): return dict( (slot, getattr(self, slot)) for slot in self.__slots__ if hasattr(self, slot) ) def __setstate__(self, state): for slot, value in state.items(): object.__setattr__(self, slot, value) # <- use object.__setattr__ b = pickle.dumps(A(5)) pickle.loads(b) 可能是一个合理的解决方法?