簡體   English   中英

腌制具有 __slots__ 的凍結數據類

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM