问题来自在设置插槽状态时pickle
使用__setattr__
实例的方法。
默认值在第 6220 行__setstate__
中定义。load_build
_pickle.c
对于状态字典中的项目,__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__
.
文档对此给出了某种警告:
使用frozen=True 时会有微小的性能损失:__init__()
不能使用简单的赋值来初始化字段,必须使用object.__setattr__()
.
__getstate__
定义为实例__dict__
始终None
在您的情况下也可能很好。如果你不这样做,state
参数__setstate__
将是一个元组(None, {'a': 5})
,第一个值是实例的值,__dict__
第二个是 slotstate 字典。
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)
我个人不会将其称为错误,因为酸洗过程被设计为灵活,但仍有增强功能的空间。酸洗协议的修订可以在未来解决这个问题。除非我遗漏了一些东西并且除了微小的性能损失之外,使用PyObject_GenericSetattr
所有插槽可能是一个合理的修复?