drh*_*gen 9 python pickle slots python-dataclasses
如何使用 pickle 冻结数据类的实例__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)
Run Code Online (Sandbox Code Playgroud)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'a'
Run Code Online (Sandbox Code Playgroud)
如果我删除frozen或__slots__. 这只是一个错误吗?
Jac*_*din 10
问题来自在设置插槽状态时pickle使用__setattr__实例的方法。
默认值在第 6220 行中__setstate__定义。load_build_pickle.c
对于state dict中的items,__dict__直接更新实例:
if (PyObject_SetItem(dict, d_key, d_value) < 0)
Run Code Online (Sandbox Code Playgroud)
而对于 slotstate dict 中的项目,__setattr__则使用实例的:
if (PyObject_SetAttr(inst, d_key, d_value) < 0)
Run Code Online (Sandbox Code Playgroud)
现在因为实例被冻结,加载时__setattr__会引发FrozenInstanceError。
为了避免这种情况,您可以定义自己的__setstate__方法,该方法将使用object.__setattr__,而不是实例的__setattr__.
该文档给出某种此警告的:
使用frozen=True 时有一个很小的性能损失:
__init__()不能使用简单的赋值来初始化字段,而必须使用object.__setattr__().
定义__getstate__实例也可能很好,因为实例__dict__始终适用None于您的情况。如果你不这样做,state参数 of__setstate__将是一个 tuple (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)
Run Code Online (Sandbox Code Playgroud)
我个人不会将其称为错误,因为酸洗过程被设计为灵活的,但仍有增强功能的空间。酸洗协议的修订可能会在未来解决这个问题。除非我遗漏了一些东西并且除了微小的性能损失之外,使用PyObject_GenericSetattr所有插槽可能是一个合理的解决方案?
Starting in Python 3.10.0, this works but only if you specify the slots via slots=True in the dataclass decorator. It does not work, and probably will never work, with __slots__ manually specified.
import pickle
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class A:
a: int
b = pickle.dumps(A(5))
pickle.loads(b) # A(a=5)
Run Code Online (Sandbox Code Playgroud)