PythonのDict/OrderedDictの環境依存について
PythonのDictionaryは順番が保証されません。なので、Dictionaryの順番を意識したいときは「OrderedDict」を使うというのが教科書的な解決方法でした。しかし、Python3.7(言語仕様化されていない状態なら3.6)からDictの順番が保証されるようになったとのことです。その環境依存についてメモしておきます。
目次
OrderedDictの場合
OrderedDictを使えばPythonのバージョンに関係なく順番が保証されます。
from collections import OrderedDict
dict = OrderedDict([["a3",0],["a2",1],["a1",2]])
dict["a3"]=-1
print(dict)
#OrderedDict([('a3', -1), ('a2', 1), ('a1', 2)])
文字通りちゃんと順番が保証されています。
通常のDictの場合
次はちょっと怪しいケースです。Python3.6.4での実行結果で通常のDictを実行してみます。
dict = {"a3":0,"a2":1,"a1":2}
dict["a3"]=-1
print(dict)
#{'a3': -1, 'a2': 1, 'a1': 2}
一見順番が保証されているように見えます。OrderedDictを使っていないのに順番が保証されるのはなぜでしょう。実はこの挙動が環境依存なのです。
StackOverFlowを読んでたら目が点でした。
From Python 3.6 onwards, the standard dict type maintains insertion order by default.
(Python3.6からは、通常のDictもデフォルトで代入の順番が維持されるようになった)
ただしPython3.6以前では通常のDictの順番が保証はされていないので、互換性の副作用を避けるためにはOrderedDictを使ったほうが良いとのこと。あくまで通常のDictの順番維持は環境依存であるということを気にしたほうが良さそうです。
より正確にいうと(別のコメントでも言及されていますが)、Dictのキーの順番維持が言語仕様化されたのはPython3.7からなので、3.7以降だけに限定するならOrderedDictを使わなくてもよくなります。
詳細な記事
調べたらもっと詳しい記事がありました。Dictのハッシュの部分から分析していますね。
Python3.6のdictキー順維持と、hash randomizeによるDoS回避の関係について
http://www.freia.jp/taka/blog/python3-hash-randomie/index.html
この記事からの引用です。
- object._hash_ のhash collisionによるDoS攻撃を回避するために、Python3.3で起動毎にhashをランダム化した
- これによって、hashテーブルの順番で並んでいた特定のdictキー列も、起動毎にランダム化された(副作用)
- Python3.6の CPython実装 で、dictキーを挿入順で維持するキー列をhashテーブルtとは別に持つようになったため、キー順が object._hash_ の結果に依存しなくなった(これは1のDoS回避と反しない)
- Pythonの言語仕様は変わっていないので、dictキーを挿入順で維持するかどうかはPython実装に依存している
はい?? んやねんそれ。以前のPythonのDictってテーブル順なってなかったの?ほげぇ…
結論
自分の中では以下のような理解になりました。
- Python3.7以降ならOrderedDictを使わなくても言語仕様上Dictの順番が維持される
- Python3.6ではだいたい順番は維持されるけど、言語仕様ではないので、ごく稀な特殊な状況で順番が維持されなくてもおかしくはない
- Python3.6以前は順番が維持されないので、普通にOrderedDictを使え
ざっとの理解なので違うかも知れません。とても勉強になりました。
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー