はじめに
pythonで複数の辞書をマージするにはどうしたらいいのでしょうか。forループ? 辞書内包表記を使う? updateメソッド? 実は、ChainMapというものもあります。
その使い方について説明します。
先に結論
記事本文でだらだらと説明していますが、要約すると、
- 複数の辞書を一つにまとめる方法
- 元辞書への参照を張ってごにょごにょするだけなので、新しい辞書を作るより(ケースバイケースだが)速い。ただしマージが速くても参照が遅いという微妙な性質がある
- 当然元辞書を変更すると反映されてしまう
これだけです。
基本的な使い方
リストを渡すのかと思ったら、違うようです。
>>> from collections import ChainMap >>> d1 = {"hoge":"ほげ"} >>> d2 = {"fuga":"ふが"} >>> cm = ChainMap(d1, d2) >>> cm["hoge"] 'ほげ' >>> cm["fuga"] 'ふが'
このように使えます。ただ、辞書のリストを動的に生成して渡したい場合もあるでしょう。その場合、starred expressionを活用して、
>>> cm = ChainMap(*[d1, d2]) >>> cm ChainMap({'hoge': 'ほげ'}, {'fuga': 'ふが'})
このようにやることになるかと思います。
参照先を変更してみる
>>> d1["HOGE"] = "ホゲ" >>> cm ChainMap({'hoge': 'ほげ', 'HOGE': 'ホゲ'}, {'fuga': 'ふが'})
このようにけっこう恐ろしい性質があります。これが嫌なときは他の方法で辞書をマージするか、deepcopyを使うことになるはずです。
速いらしいという噂があるので測る
測ってみました。上記参考サイトの辞書内包表記版と比較します。
予想では、マージは速いものの探索は恐らくhashのリストを線形探索していく上、重複を取り除く処理などもあるので、遅い要素がありそうです。
items()と一つのキーへのアクセスで近似的に探索を表現します。
# coding: UTF-8 import time from collections import ChainMap def time_measure(f, lst): time_lst = [] for i in range(10000): t1 = time.time() result = f(lst) t2 = time.time() time_lst.append(t2-t1) return result, sum(time_lst)/10000 def main(): f1 = lambda dicts:{k: v for dic in dicts for k, v in dic.items()} f2 = lambda dicts:ChainMap(*dicts) d1 = dict(zip(range(100), range(100))) d2 = dict(zip("ho", "ge")) d3 = dict(zip(range(100), "p"*100)) result, time = time_measure(f1, [d1, d2, d3]) print("辞書内包マージ", time) f = lambda _:[(k,v) for k,v in result.items()] _, time = time_measure(f, None) print("辞書内包items()", time) f = lambda _:result["h"] _, time = time_measure(f, None) print("辞書内包キーアクセス", time) result, time = time_measure(f2, [d1, d2, d3]) print("ChainMapマージ", time) f = lambda _:[(k,v) for k,v in result.items()] _, time = time_measure(f, None) print("ChainMap items()", time) f = lambda _:result["h"] _, time = time_measure(f, None) print("ChainMapキーアクセス", time) if __name__ == "__main__": main()
結果は
辞書内包マージ 1.2324166297912597e-05 辞書内包items() 7.125544548034668e-06 辞書内包キーアクセス 1.9309520721435546e-07 ChainMapマージ 1.3034582138061524e-06 ChainMap items() 6.01630687713623e-05 ChainMapキーアクセス 1.183009147644043e-06
マージは若干遅いが探索は速い辞書、マージは速いものの肝心の探索が遅いChainMapという結論。
探索速度(実タスクでの探索速度ではないが)に1桁の差が出てる訳で、安直に「ChainMap使おう」とはならないと思う・・・。普通はアクセスの速さを享受したいから辞書にすると思うのだが。
まとめ
使い所は難しいと思いました。