小能豆

为什么 python 字典会改变顺序?

py

python3.5 中字典中存储的对象的顺序在解释器的不同执行过程中会发生变化,但对于同一个解释器实例来说,它似乎保持不变。

$ python3 <(printf 'print({"a": 1, "b": 2})\nprint({"a": 1, "b": 2})\nprint({"a": 1, "b": 2})\nprint({"a": 1, "b": 2})')
{'b': 2, 'a': 1}
{'b': 2, 'a': 1}
{'b': 2, 'a': 1}
{'b': 2, 'a': 1}
$ python3 <(printf 'print({"a": 1, "b": 2})\nprint({"a": 1, "b": 2})\nprint({"a": 1, "b": 2})\nprint({"a": 1, "b": 2})')
{'a': 1, 'b': 2}
{'a': 1, 'b': 2}
{'a': 1, 'b': 2}
{'a': 1, 'b': 2}

我一直以为顺序是基于密钥的哈希值。为什么在不同的 Python 执行过程中顺序会有所不同?


阅读 9

收藏
2024-11-19

共1个答案

小能豆

这是因为从 Python 3.3 开始,字典中键的哈希值由 随机化哈希种子(Hash Randomization Seed)影响,每次启动 Python 解释器时,这个种子都会重新生成。这会导致在不同的解释器执行过程中,即使是相同的字典定义,其键的顺序可能也会发生变化。


为什么同一进程内顺序保持不变?

在单个 Python 进程中,随机哈希种子在解释器启动时固定。因此,字典中键的哈希值和插入顺序是确定的,这会使同一进程内同样的字典在不同位置出现时顺序保持一致。你运行的以下代码证明了这一点:

$ python3 <(printf 'print({"a": 1, "b": 2})\nprint({"a": 1, "b": 2})\n')
{'b': 2, 'a': 1}
{'b': 2, 'a': 1}

在同一进程中,由于使用相同的哈希种子,所有字典的键顺序是一致的。


为什么在不同的执行过程中顺序会变化?

每次启动 Python 解释器时,都会生成一个新的随机种子,导致键的哈希值不同,因此字典的顺序可能变化。例如:

$ python3 <(printf 'print({"a": 1, "b": 2})')
{'b': 2, 'a': 1}

$ python3 <(printf 'print({"a": 1, "b": 2})')
{'a': 1, 'b': 2}

在每次新启动的 Python 进程中,种子随机化导致键的哈希值发生变化,从而引起顺序不同。


为什么字典顺序依赖于哈希值?

Python 字典底层使用哈希表(hash table)实现。键的哈希值决定它在表中的存储位置。以下是流程:

  1. 计算键的哈希值:每个键都会生成一个哈希值。
  2. 确定插入位置:哈希值通过某种哈希函数映射到存储位置。
  3. 顺序依赖于哈希值:哈希值的变化会直接影响键的顺序。

随机化种子通过影响哈希值改变了插入位置。


如何验证哈希值的变化?

你可以通过以下代码显示哈希值,观察其在不同执行中的变化:

print(hash("a"))  # 查看键 "a" 的哈希值
print(hash("b"))  # 查看键 "b" 的哈希值

在两次不同的执行中,结果可能不同:

运行 1

764128340
1829473081

运行 2

-654892031
1298751043

哈希值的不同直接导致字典键的顺序变化。


如何禁用随机化哈希种子?

你可以通过设置环境变量来禁用哈希随机化:

PYTHONHASHSEED=0 python3 script.py

这将强制所有哈希值在每次执行时都一致,从而使字典键的顺序固定。


总结

  • 随机化种子:在解释器启动时重新生成,影响键的哈希值。
  • 同一进程内一致:因为种子不变。
  • 跨执行变化:每次执行的种子不同。
  • 禁用随机化:可用 PYTHONHASHSEED 环境变量实现。
2024-11-19