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))
ここまで書けば、きっと忘れない。