[英]Pickle a frozen dataclass that has __slots__
如何使用__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__
这会起作用。 这只是一个错误吗?
问题来自在设置插槽状态时使用实例的__setattr__
方法进行pickle
。
默认__setstate__
定义在load_build
在_pickle.c
线6220 。
对于状态字典中的项,实例__dict__
直接更新:
if (PyObject_SetItem(dict, d_key, d_value) < 0)
而对于 slotstate dict 中的项目,则使用实例的__setattr__
:
if (PyObject_SetAttr(inst, d_key, d_value) < 0)
现在因为实例被冻结, __setattr__
FrozenInstanceError
在加载时引发FrozenInstanceError
。
为了避免这种情况,您可以定义自己的__setstate__
方法,该方法将使用object.__setattr__
,而不是实例的__setattr__
。
文档对此给出了某种警告:
使用frozen=True 时有一个很小的性能损失:
__init__()
不能使用简单的赋值来初始化字段,而必须使用object.__setattr__()
。
定义__getstate__
也可能很好,因为实例__dict__
在您的情况下始终为None
。 如果不这样做, __setstate__
的state
参数将是一个元组(None, {'a': 5})
,第一个值是实例的__dict__
的值,第二个值是 slotstate dict 的值。
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
可能是一个合理的解决方案?
从 Python 3.10.0 开始,这有效,但前提是您通过数据类装饰器中的slots=True
指定插槽。 它不起作用,并且可能永远不会起作用,手动指定__slots__
。
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)
如果您只需要该类是可散列的,您可以使用unsafe_hash=True
选项强制生成__hash__
函数。 你不会得到不变性保证,但无论如何在 python 中不变性是不可能的。
相关的python文档说明:
虽然不建议,您可以强制
dataclass()
来创建一个__hash__()
与方法unsafe_hash=True
。 如果您的类在逻辑上是不可变的,但仍然可以改变,则可能就是这种情况。 这是一个专门的用例,应该仔细考虑。
import pickle
from dataclasses import dataclass
@dataclass(unsafe_hash=True)
class A:
__slots__ = ('a',)
a: int
b = pickle.dumps(A(5))
hash(pickle.loads(b)) # works and can hash!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.