web-dev-qa-db-ja.com

辞書の順序が非決定論的であるのはなぜですか?

最近、Python 2.7からPython 3.3に切り替えましたが、Python 2の場合、辞書キーの順序はPython 3では、たとえば vars() で取得された辞書のキーの順序は、非決定的であるように見えます。

私が実行した場合:

class Test(object): pass
parameters = vars(Test)
print(list(parameters.keys()))

Python 2.7とPython 3.3の場合、次のようになります。

  • Python2.7は一貫して私に

    ['__dict__', '__module__', '__weakref__', '__doc__']
    
  • Python 3.3を使用すると、任意のランダムな順序を取得できます。例:

    ['__weakref__', '__module__', '__qualname__', '__doc__', '__dict__']
    ['__doc__', '__dict__', '__qualname__', '__module__', '__weakref__']
    ['__dict__', '__module__', '__qualname__', '__weakref__', '__doc__']
    ['__weakref__', '__doc__', '__qualname__', '__dict__', '__module__']
    

この非決定論はどこから来たのですか?そして、なぜ

list({str(i): i for i in range(10)}.keys())

…実行間で一貫性があり、常に

['3', '2', '1', '0', '7', '6', '5', '4', '9', '8']

…?

38
Anaphory

更新:Python 3.6、dictには 新しい実装 これは挿入順序を保持します。Python 3.7から、この順序保持動作は 保証

dict オブジェクトの挿入順序の保存の性質 宣言されています Python言語仕様の公式部分です。


これは、2012年の セキュリティ修正 の結果です。これは デフォルトで有効 in Python 3.3(「セキュリティの改善」までスクロールダウン) )。

発表から:

ハッシュのランダム化により、dictとセットの反復順序が予測不能になり、Python実行間で異なります。Pythonは、dictまたはセット内のキーの反復順序を保証していません。 、およびアプリケーションは決してそれに依存しないことをお勧めします。歴史的に、dictの反復順序はリリース間であまり頻繁に変更されず、Pythonの連続実行間で常に一貫性を保っています。したがって、一部の既存のアプリケーションはdictまたはセットの順序に依存している可能性があります。これと、信頼できない入力を受け入れない多くのPythonアプリケーションは、この攻撃に対して脆弱ではないという事実、ここで言及されているすべての安定したPythonリリース、ハッシュランダム化ISデフォルトで無効になっています。

上記のように、最後の大文字のビットはPython 3.3では真ではなくなりました。

関連項目:object.__hash__()ドキュメント (「メモ」サイドバー)。

どうしても必要な場合は、 PYTHONHASHSEED 環境変数を0に設定することで、この動作の影響を受けるバージョンのPython)でハッシュランダム化を無効にできます。 。


あなたの反例:

list({str(i): i for i in range(10)}.keys())

…doesnot実際には、Python 3.3で常に同じ結果が得られますが、異なる順序の数は制限されています のため ハッシュ衝突の処理方法:

$ for x in {0..999}
> do
>   python3.3 -c "print(list({str(i): i for i in range(10)}.keys()))"
> done | sort | uniq -c
     61 ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
     73 ['1', '0', '3', '2', '5', '4', '7', '6', '9', '8']
     62 ['2', '3', '0', '1', '6', '7', '4', '5', '8', '9']
     59 ['3', '2', '1', '0', '7', '6', '5', '4', '9', '8']
     58 ['4', '5', '6', '7', '0', '1', '2', '3', '8', '9']
     55 ['5', '4', '7', '6', '1', '0', '3', '2', '9', '8']
     62 ['6', '7', '4', '5', '2', '3', '0', '1', '8', '9']
     63 ['7', '6', '5', '4', '3', '2', '1', '0', '9', '8']
     60 ['8', '9', '0', '1', '2', '3', '4', '5', '6', '7']
     66 ['8', '9', '2', '3', '0', '1', '6', '7', '4', '5']
     65 ['8', '9', '4', '5', '6', '7', '0', '1', '2', '3']
     53 ['8', '9', '6', '7', '4', '5', '2', '3', '0', '1']
     62 ['9', '8', '1', '0', '3', '2', '5', '4', '7', '6']
     52 ['9', '8', '3', '2', '1', '0', '7', '6', '5', '4']
     73 ['9', '8', '5', '4', '7', '6', '1', '0', '3', '2']
     76 ['9', '8', '7', '6', '5', '4', '3', '2', '1', '0']

この回答の冒頭で述べたように、Python 3.6:

$ for x in {0..999}
> do
>   python3.6 -c "print(list({str(i): i for i in range(10)}.keys()))"
> done | sort | uniq -c
   1000 ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
48
Zero Piraeus

Python 3.7にはまだ非決定論的なセットがありますが、dictは挿入順序を保持しますが、セットは保持しません。セットは同じランダムな動作を示す可能性があることに注意してください。

python3 -c "print({str(i) for i in range(9)})"

それでも、実行ごとに異なる結果が得られます。

4
Pete Cacioppi