[英]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.