【问题标题】:Why is dictionary ordering non-deterministic?为什么字典排序是不确定的?
【发布时间】:2023-12-31 00:27:02
【问题描述】:

我最近从 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 中,则:

  • Python 2.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']

…?

【问题讨论】:

    标签: python dictionary python-3.x python-3.3 non-deterministic


    【解决方案1】:

    更新:在 Python 3.6 中,dict 有一个 new implementation,它保留了插入顺序。从 Python 3.7 开始,这种保序行为是 guaranteed

    dict 对象 has been declared 的插入顺序保留性质成为 Python 语言规范的正式部分。


    这是 2012 年 security fix 的结果,在 Python 3.3 中是 enabled by default(向下滚动到“安全性改进”)。

    来自公告:

    哈希随机化导致dicts和sets的迭代顺序为 不可预测并且在 Python 运行中有所不同。 Python从来没有保证 字典或集合中键的迭代顺序,建议应用程序永远不要 依靠它。从历史上看,dict 迭代顺序并没有经常改变 发布并在连续执行之间始终保持一致 Python。因此,一些现有的应用程序可能依赖于 dict 或 set 排序。 因为这个以及许多不接受的 Python 应用程序的事实 在所有稳定的 Python 版本中,不受信任的输入都不容易受到这种攻击 这里提到,HASH RANDOMIZATION 是默认禁用的。

    如上所述,最后一个大写的位在 Python 3.3 中不再适用。

    另请参阅: object.__hash__() documentation(“注意”侧边栏)。

    如果绝对必要,您可以通过将PYTHONHASHSEED 环境变量设置为0,在受此行为影响的 Python 版本中禁用哈希随机化。


    你的反例:

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

    ... not 实际上在 Python 3.3 中总是给出相同的结果,尽管不同排序的数量是有限的due to 处理哈希冲突的方式:

    $ 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']
    

    【讨论】:

    【解决方案2】:

    请注意,Python 3.7 仍然有非确定性集合。 dicts 保留插入顺序,但集合不保留。集合可以表现出相同的随机行为。

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

    从一次运行到下一次运行仍然给出不同的结果。

    【讨论】:

    • 来这里发布答案,因为我发现这个很难......然后我看到了这个...... +1
    最近更新 更多