defaultdictのdefaultdict

pythonの標準ライブラリにdefaultdictという便利な道具がある。
ところでたまにdefaultdictのdefaultdictをやりたくなるが、いつも忘れていつも同じこのstackoverflowの記事に流れ着く。
毎度毎度検索しているので、ちゃんとメモればきっと忘れないはず。

普通の辞書ならこう

>>> d = {}
>>> print d["foo"]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'foo'

で、defaultdictならこう

>>> from collections import defaultdict
>>> d = defaultdict(list)
[]
>>> d2 = defaultdict(int)
>>> print d2["bar"]
0

じゃあdefaultdictのdefaultdictなら、こう、と思ったら大間違い

>>> d = defaultdict(defaultdict(int))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: first argument must be callable

エラーが出てる。内容を読むと、 TypeError: first argument must be callable
とあるので、callableにしてしまおう。

>>> print d["foo"]
defaultdict(<type 'int'>, {})
>>> print d["foo"]["bar"]
0

なんかよくわからないけど動いた。これが答え。

さて、気持ち悪いから理由を知りたいという人は、前述のstackoverflowの記事を読むと良くて、きちんとなんでこれで動くのかにも触れている。

これをざっくりまとめると、defaultdict(some_type)はキーが無いときに、some_type()をcallする仕組みになってるから、という話。
馴染み深いint, float, str, unicode, dict, listなどの組み込み関数は、大抵型変換の時に使うが、これを引数なしメソッドとしてcallすると、零元相当(といっていいかは微妙だが。boolとかさ...)のものが得られる。

>>> int()
0
>>> list()
[]
>>> float()
0.0
>>> dict()
{}
>>> str()
''
>>> unicode()
u''
>>> bool()
False

ここで

defaultdict(defaultdict(int))

としてしまうと、デフォルト値を得るための内部の仕組みがdefaultdict(int)をメソッドとして呼ぼうとする。つまり、

defaultdict(int)()

こんな感じのコードが走ってエラーになる。
defaultdict(int)の空のインスタンスがデフォルト値になれば良いのだから、それを返す引数なし関数をdefaultdictに渡してやれば良い。つまり、以下のように書けば良い。

defaultdict(lambda: defaultdict(int))

ここまで書けば、きっと忘れない。