Pythonのpickleでserializeできない問題の回避策(dill)

読み込んだデータをシリアライズしておきたい場合、pickleを使えば任意のクラスのオブジェクトを扱える。ファイルを開いて、dumpで書き出し、loadで読み出しを行えば良い。しかし、ある構造を持つクラスのオブジェクトをpickleでシリアライズしようとすると、以下のようにエラーとなる。

$ python --version
Python 3.4.3

$ python pickle_test.py 
Traceback (most recent call last):
  File "pickle_test.py", line 15, in <module>
    pickle.dump(klass,f)
_pickle.PicklingError: Can't pickle <function Klass.__init__.<locals>.<lambda> at 0x107728ae8>: attribute lookup <lambda> on __main__ failed

クラスの内容は以下の通りで、defaultdictをネストしているので、lambdaを与えている。

$ cat pickle_test.py
# coding: utf-8

import pickle
from collections import defaultdict

class Klass:
    def __init__(self):
        self.prop = defaultdict(lambda:defaultdict(int))

klass = Klass()

klass.prop["x"]["y"] += 10

with open("test.pickle","wb") as f:
    pickle.dump(klass,f)

with open("test.pickle","rb") as f:
    newklass = pickle.load(f)

print(newklass.prop["x"]["y"])

以下をみると、同じようなエラーがlambdaを使った際に発生するようだ。

PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed - あと5分だけ

そこで、defaultdictの引数にlambdaではなく関数を渡すようにするとうまくいく。一応、defaultdictのネストにも対応できる。もう少し賢いやり方があるかもしれない。

$ cat pickle_test2.py 
# coding: utf-8

import pickle
from collections import defaultdict

class Val:
    def __init__(self):
        self.vprop= defaultdict(int)

def ddv():
    return defaultdict(Val)

def dd2():
    return defaultdict(dd)

def dd():
    return defaultdict(int)

class Klass:
    def __init__(self):
        self.prop = defaultdict(dd)
        self.prop2= defaultdict(dd2)
        self.prop3= defaultdict(ddv)

klass = Klass()

klass.prop["x"]["y"] += 10
klass.prop2["x"]["y"]["z"] += 100
klass.prop3["x"]["y"].vprop["z"] += 1000

with open("test.pickle","wb") as f:
    pickle.dump(klass,f)

with open("test.pickle","rb") as f:
    newklass = pickle.load(f)

print(newklass.prop["x"]["y"])
print(newklass.prop2["x"]["y"]["z"])
print(newklass.prop3["x"]["y"].vprop["z"])

$ python pickle_test2.py 
10
100
1000

追記 dill

コメントで教えていただきましたdillを使えば良いことが分かりました。もうpickle必要無さそうですね。dillはpipでインストールします。

$ cat dill_test.py
# coding: utf-8

import dill
from collections import defaultdict

class Val:
    def __init__(self):
        self.vprop= defaultdict(int)

class Klass:
    def __init__(self):
        self.prop = defaultdict(lambda:defaultdict(int))
        self.prop2= defaultdict(lambda:defaultdict(lambda:defaultdict(int)))
        self.prop3= defaultdict(lambda:defaultdict(Val))

klass = Klass()

klass.prop["x"]["y"] += 10
klass.prop2["x"]["y"]["z"] += 100
klass.prop3["x"]["y"].vprop["z"] += 1000

with open("test.pickle","wb") as f:
    dill.dump(klass,f)

with open("test.pickle","rb") as f:
    newklass = dill.load(f)

print(newklass.prop["x"]["y"])
print(newklass.prop2["x"]["y"]["z"])
print(newklass.prop3["x"]["y"].vprop["z"])
$ pip install dill
...

$ python dill_test.py 
10
100
1000