辞書型で順序を保つ方法 OrderedDict

この記事を書いた時点、Python3.5では辞書型は順序を保たなかったのですが、標準Python(CPython)では3.6から順序が保たれるようになりました。また、その他の実装のPythonでも3.7以降順序が保たれるようになったため、最新版を使用されている方にとってこの記事は不要です。

以下、公式からの引用です。

https://docs.python.org/ja/3/library/stdtypes.html#dict
バージョン 3.7 で変更: 辞書の順序が挿入順序であることが保証されるようになりました。この振る舞いは CPython 3.6 の実装詳細でした。

dict型は順序を保たない

Python3.5以前のdict型は順序を保ちません。

data_dict = {'apple': 100, 'orange': 80, 'banana': 70}
for key, val in data_dict.items():
    print(key, val) # 実行するたびに順序が変わる

Python3.5で上のスクリプトを何度か実行してみてください。実行するたびに出力される順序が変わることが確認できます。

順序を保つOrderedDict

さて、python標準ライブラリのcollectionsモジュールにはOrderedDictという順序を保つ辞書型が用意されています。順序を保った辞書が必要な場合はこの型を使用しましょう。

from collections import OrderedDict
data_dict = OrderedDict()
data_dict['apple'] = 100
data_dict['orange'] = 80
data_dict['banana'] = 70

# 通常の辞書と同じように使用できる
for key, val in data_dict.items():
    print(key, val)

今度は何度実行しても追加した順番で出力されることが確認できたと思います。OrderedDictはキーが追加された順序を記憶するため、追加済みのキーに対して値を上書きしても挿入位置は保たれたままとなります。ただし、一旦項目を削除して再挿入すると最後に追加されます。

コンストラクタの注意点

コンストラクタのキーワード引数で辞書の要素を予め指定できるのですが、この方法はおすすめしません。というのも、公式リファレンスにも記述があるとおり、この方法だと順序が保たれないからです。

コンストラクタと update() メソッドは、どちらもキーワード引数を受け付けますが、その順序は失われます。これは、Python の関数呼び出しの意味づけにおいて、キーワード引数は順序付けされていない通常の辞書を用いて渡されるからです。

以下のスクリプトを何度か実行してみると、やはり実行するたびに順序が変わることが確認できます。

data_dict = OrderedDict(apple= 100, orange = 80, banana = 70)
for key, val in data_dict.items():
    print(key, val) # 実行するたびに順序が変わる

ただし、キーワード引数の代わりに組のシーケンスを使用することができ、この場合は順序が保たれます。

datas = [['apple', 100], ['orange', 80], ['banana', 70]]
data_dict = OrderedDict(datas)
for key, val in data_dict.items():
    print(key, val)