CythonのPickle化

ポインタをもつクラスをPickle化しようとしたらエラーを吐いたので、その対処法のメモ。

__getstate__メソッドの実装

ポインタにしているオブジェクトを復元できるだけの情報を一旦Pythonオブジェクトのメンバにして、__dict__stateとして返す。

__setstate__メソッドの実装

__getstate__で吐き出した情報からポインタオブジェクトを再作成。

実装例

cdef class SomeClass:
    cdef CppClass* cpp
    def __init__(self, id: int) -> None: # イニシャライザ
         self.cpp = new CppClass(id)


    def __dealloc__(self) -> None: # new したメモリの解放
        del self.cpp


    def __getstate__(self): # pickle化する時に呼び出される
        self.id = self.cpp.getid() # 復元できるだけの情報を取得
        state = self.__dict__.copy() 
        del self.id # 不要なオブジェクトを削除
        return state


    def __setstate__(self, state): # unpickle化する時に呼び出される
        self.__dict__.update(state)
        self.cpp = new CppClass(self.id) # 再作成。必要があればセッターを使って復元する
        del self.id

その他

より低レベルなPickleの実装として、__reduce__があるけど、おそらく可読性や実装の楽さで__getstate__, __setstate__が勝る。

というより、__reduce__でリビルダーを上手く実装できなかった。

参考資料

pickle --- Python オブジェクトの直列化 — Python 3.9.1 ドキュメントより引用

object.__getstate__()
クラスはそのインスタンスをどう pickle 化するかについてさらに影響を与えることができます; クラスに __getstate__() メソッドが定義されていた場合それが呼ばれ、返り値のオブジェクトはインスタンスの辞書ではなく、インスタンスの内容が pickle 化されたものになります。__getstate__() がないときは通常通りインスタンスの __dict__ が pickle 化されます。

object.__setstate__(state)
非 pickle 化に際して、クラスが __setstate__() を定義している場合、それは非 pickle 化された状態とともに呼び出されます。その場合、状態オブジェクトが辞書でなければならないという要求はありません。そうでなければ、 pickle された状態は辞書で、その要素は新しいインスタンスの辞書に割り当てられます。

__getstate__() および __setstate__() メソッドの使い方に関する詳細な情報については 状態を持つオブジェクトの扱い 節を参照してください。