web-dev-qa-db-ja.com

defaultdictをピクルできません

私はこのようなdefaultdictを持っています:

dict1 = defaultdict(lambda: defaultdict(int))

問題は、cPickleを使用してピクルすることができないことです。ここで私が見つけた解決策の1つは、ラムダの代わりにモジュールレベルの関数を使用することです。私の質問は、モジュールレベルの関数とは何ですか? cPickleで辞書を使用するにはどうすればよいですか?

49
Fynn Mahoney

Martijnの説明に加えて

モジュールレベルの関数は、モジュールレベルで定義された関数です。つまり、クラスのインスタンスメソッドではなく、別の関数内にネストされておらず、ラムダ関数ではなく名前を持つ「実際の」関数です。

したがって、defaultdictをピクルするには、ラムダ関数の代わりにモジュールレベルの関数を使用して作成します。

def dd():
    return defaultdict(int)

dict1 = defaultdict(dd) # dd is a module-level function

あなたがそれを漬けることができるよりも

tmp = pickle.dumps(dict1) # no exception
new = pickle.loads(tmp)
53
sloth

Pickleはすべてのインスタンス属性を保存する必要があり、defaultdictインスタンスはdefault呼び出し可能オブジェクトへの参照を保存します。 Pickleは各インスタンス属性に対して再帰します。

ピクルはラムダを処理できません。 pickleは常にデータを処理し、コードは処理しません。ラムダにはコードが含まれます。関数canはpickle化できますが、関数がimportedである場合にのみ、クラス定義と同じです。モジュールレベルで定義された関数をインポートできます。その場合、Pickleは文字列を格納するだけです。つまり、再度ピックル解除するときにインポートおよび参照される関数の完全な「パス」です。

16
Martijn Pieters

ただし、partialを使用してこれを実現できます。

>>> from collections import defaultdict
>>> from functools import partial
>>> pickle.loads(pickle.dumps(defaultdict(partial(defaultdict, int))))
defaultdict(<functools.partial object at 0x94dd16c>, {})
12
jamylak

これを行うには、記述したいコードを記述します。私は dill を使用します。これはラムダとdefaultdictsをシリアル化できます。ディルは、Pythonのほとんどすべてをシリアル化できます。

>>> import dill
>>> from collections import defaultdict
>>>
>>> dict1 = defaultdict(lambda: defaultdict(int))
>>> pdict1 = dill.dumps(dict1)
>>> _dict1 = dill.loads(pdict1)
>>> _dict1
defaultdict(<function <lambda> at 0x10b31b398>, {})
7
Mike McKerns

Defaultdictタイプを保持する必要がない場合は、変換します。

fname = "file.pkl"

for value in nested_default_dict:
    nested_default_dict[value] = dict(nested_default_dict[value])
my_dict = dict(nested_default_dict)

with open(fname, "wb") as f:
    pickle.dump(my_dict, f)  # Now this will work

あなたが漬物をしているとき、オブジェクトはおそらく最終的な形であるため、これは素晴らしい代替案だと思います...そして、本当にdefaultdict型が本当に必要な場合は、取り出した後に単純に変換できます:

for value in my_dict:
    my_dict[value] = defaultdict(type, my_dict[value])
nested_default_dict = defaultdict(type, my_dict)
2
user2921352
dict1 = defaultdict(lambda: defaultdict(int))
cPickle.dump(dict(dict1), file_handle)

私のために働いた

2
Avi

通常の関数で匿名のラムダ関数を実装すると、うまくいきました。マイクが指摘したように、ピクルスはラムダを処理できません。 pickleはデータのみを扱います。したがって、defaultdictメソッドを次のように変換します。

    dict_ = defaultdict(lambda: default_value)

に:

    def default_():
        return default_value

そして、次のようにデフォルトの辞書を作成するとうまくいきました:

    dict_ = defaultdict(default_)
1

私は現在、質問poserと同様のことをしていますが、default_factoryとして使用されるメンバー関数を持つdefaultdictのサブクラスを使用しています。コードを適切に機能させるために(実行時に関数を定義する必要がありました)、オブジェクトを酸洗いする準備をするコードをいくつか追加しました。

の代わりに:

...
pickle.dump(dict, file)
...

私はこれを使います:

....
factory = dict.default_factory
dict.default_factory = None
pickle.dump(dict, file)
dict.default_factory = factory
...

私のツリーはインデックスが要求されたときにツリーのタイプと同じインスタンスを作成するオブジェクトであるため、これは私が使用した正確なコードではありません(したがって、再帰メンバー関数を使用して事前/事後ピクル操作を実行します)が、このパターンも質問に答えます。

1
Sandy Chapman