pickle
reference states that pickle化できるオブジェクトのセットはかなり制限されています。実際、動的に生成されたクラスを返す関数があり、そのクラスのインスタンスをピクルできないことがわかりました。
>>> import pickle
>>> def f():
... class A: pass
... return A
...
>>> LocalA = f()
>>> la = LocalA()
>>> with open('testing.pickle', 'wb') as f:
... pickle.dump(la, f, pickle.HIGHEST_PROTOCOL)
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: Can't pickle local object 'f.<locals>.A'
このようなオブジェクトは、pickle
には複雑すぎます。 OK。さて、魔法とは、同様のオブジェクトをピクルスしようとしても、派生クラスの場合、機能するということです!
>>> class DerivedA(LocalA): pass
...
>>> da = DerivedA()
>>> with open('testing.pickle', 'wb') as f:
... pickle.dump(da, f, pickle.HIGHEST_PROTOCOL)
...
>>>
ここで何が起こっていますか?これが非常に簡単な場合、なぜpickle
はこの回避策を使用して、「ローカルオブジェクト」をピクルできるdump
メソッドを実装しないのでしょうか。
引用文献 を注意深く読んでいないと思います。また、このリファレンスには、次のオブジェクトのみが漬物可能であることが明記されています。
- モジュールの最上位で定義された関数(> lambdaではなくdefを使用)
- モジュールのトップレベルで定義された組み込み関数
- モジュールの最上位で定義されているクラス
あなたの例
_>>> def f():
... class A: pass
... return A
_
モジュールの最上位でクラスを定義するのではなく、f()
の-scope内でクラスを定義します。 pickle
は、ローカルクラスではなくグローバルクラスで機能します。これは、pickleableテストに自動的に失敗します。
DerivedA
はグローバルクラスなので、すべて順調です。
なぜトップレベル(グローバル)のクラスと関数のみがpickle化できないのかについては、リファレンスもその質問に答えています(太字のもの):
関数(組み込みおよびユーザー定義)は、値ではなく「完全修飾」名前参照によってピクルされることに注意してください。これは、関数が定義されているモジュールの名前とともに、関数名のみがピクルされることを意味します。関数のコードもその関数属性もピクルされません。したがって、定義するモジュールは、ピッキング解除環境にインポート可能でなければならず、モジュールには名前付きオブジェクトが含まれている必要があります。そうでない場合、例外が発生します。
同様に、クラスは名前付き参照によってピクルされるため、アンピクル環境で同じ制限が適用されます。
だからあなたはそれを持っています。 pickle
は、オブジェクトに含まれる生の命令ではなく、名前の参照によってのみオブジェクトをシリアル化します。これは、_pickle's
_ジョブがオブジェクト階層をシリアル化するためであり、それ以外は何もしないためです。
私は同意しません、あなたは両方を漬けることができます。 dill
のような、より優れたシリアライザーを使用する必要があります。 dill
(デフォルト)は、参照によるピクルではなくクラス定義を保存することでクラスをピクルするため、最初のケースで失敗することはありません。必要に応じて、dill
を使用してソースコードを取得することもできます。
>>> import dill as pickle
>>> def f():
... class A: pass
... return A
...
>>> localA = f()
>>> la = localA()
>>>
>>> _la = pickle.dumps(la)
>>> la_ = pickle.loads(_la)
>>>
>>> class DerivedA(localA): pass
...
>>> da = DerivedA()
>>> _da = pickle.dumps(da)
>>> da_ = pickle.loads(_da)
>>>
>>> print(pickle.source.getsource(la_.__class__))
class A: pass
>>>
DerivedA
は、その完全修飾名に一致するグローバル変数を介してDerivedA
を使用できるため、ピクル可能です。これは、pickle
がピッキング解除時にクラスを検索する方法です。
ローカルクラスでこのようなことをしようとすることの問題は、インスタンスが対応するwhichA
クラスを識別するものがないことです。 f
を2回実行すると、2つのA
クラスが得られますが、プログラムの別の実行から、どちらがピクルされていないA
インスタンスのクラスであるかを知る方法はありません。 f
をまったく実行しない場合、noA
クラスが得られます。そして、ピクルされていないインスタンスのタイプについて何をしますか?
モジュールのトップレベルで定義されたクラスのインスタンスのみをピクルできます。
ただし、ローカルで定義されたクラスのインスタンスをトップレベルに昇格させると、そのインスタンスをピクルできます。
ローカルクラスの__ qualname __ class属性を設定する必要があります。次に、クラスを同じ名前のトップレベル変数に割り当てる必要があります。
def define_class(name):
class local_class:
pass
local_class.__qualname__ = name
return local_class
class_A = define_class('class_A') # picklable
class_B = define_class('class_B') # picklable
class_X = define_class('class_Y') # unpicklable, names don't match