Проблема возникает из pickle
, использующего метод __setattr__
экземпляра при настройке состояния слотов.
Значение по умолчанию __setstate__
определено в load_build
в _pickle.c
строке 6220 .
Для элементов в состоянии dict экземпляр __dict__
имеет видобновлено напрямую:
if (PyObject_SetItem(dict, d_key, d_value) < 0)
, тогда как для элементов в слоте состояния слота используется экземпляр __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__
экземпляра, а второе - dicttate.
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
для всех слотов может быть разумным решением?