web-dev-qa-db-ja.com

派生クラスを使用すると、「ローカルオブジェクトをピクル」できますか?

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メソッドを実装しないのでしょうか。

24
fonini

引用文献 を注意深く読んでいないと思います。また、このリファレンスには、次のオブジェクトのみが漬物可能であることが明記されています。

  • モジュールの最上位で定義された関数(> lambdaではなくdefを使用)
  • モジュールのトップレベルで定義された組み込み関数
  • モジュールの最上位で定義されているクラス

あなたの例

_>>> def f():
...     class A: pass
...     return A
_

モジュールの最上位でクラスを定義するのではなく、f()の-​​scope内でクラスを定義します。 pickleは、ローカルクラスではなくグローバルクラスで機能します。これは、pickleableテストに自動的に失敗します。

DerivedAはグローバルクラスなので、すべて順調です。

なぜトップレベル(グローバル)のクラスと関数のみがpickle化できないのかについては、リファレンスもその質問に答えています(太字のもの):

関数(組み込みおよびユーザー定義)は、値ではなく「完全修飾」名前参照によってピクルされることに注意してください。これは、関数が定義されているモジュールの名前とともに、関数名のみがピクルされることを意味します。関数のコードもその関数属性もピクルされません。したがって、定義するモジュールは、ピッキング解除環境にインポート可能でなければならず、モジュールには名前付きオブジェクトが含まれている必要があります。そうでない場合、例外が発生します。

同様に、クラスは名前付き参照によってピクルされるため、アンピクル環境で同じ制限が適用されます。

だからあなたはそれを持っています。 pickleは、オブジェクトに含まれる生の命令ではなく、名前の参照によってのみオブジェクトをシリアル化します。これは、_pickle's_ジョブがオブジェクト階層をシリアル化するためであり、それ以外は何もしないためです。

31
Akshat Mahajan

私は同意しません、あなたは両方を漬けることができます。 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

>>> 
19
Mike McKerns

DerivedAは、その完全修飾名に一致するグローバル変数を介してDerivedAを使用できるため、ピクル可能です。これは、pickleがピッキング解除時にクラスを検索する方法です。

ローカルクラスでこのようなことをしようとすることの問題は、インスタンスが対応するwhichAクラスを識別するものがないことです。 fを2回実行すると、2つのAクラスが得られますが、プログラムの別の実行から、どちらがピクルされていないAインスタンスのクラスであるかを知る方法はありません。 fをまったく実行しない場合、noAクラスが得られます。そして、ピクルされていないインスタンスのタイプについて何をしますか?

2
user2357112

モジュールのトップレベルで定義されたクラスのインスタンスのみをピクルできます。

ただし、ローカルで定義されたクラスのインスタンスをトップレベルに昇格させると、そのインスタンスをピクルできます。

ローカルクラスの__ 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
2
haael