暗示mgilson 的好答案的关键一件事,但在任何现有答案中都没有明确提及:
小整数自己散列:
>>> [hash(x) for x in (1, 2, 3, 88)]
[1, 2, 3, 88]
字符串散列为不可预测的值。事实上,从 3.3 开始,默认情况下,它们是基于启动时随机分配的种子构建的。因此,对于每个新的 Python 解释器会话,您都会得到不同的结果,但是:
>>> [hash(x) for x in 'abcz']
[6014072853767888837,
8680706751544317651,
-7529624133683586553,
-1982255696180680242]
因此,考虑最简单的哈希表实现:只是一个包含 N 个元素的数组,其中插入一个值意味着将其放入hash(value) % N
(假设没有冲突)。你可以粗略地猜测N
会有多大——它会比其中的元素数量大一点。当从 6 个元素的序列中创建一个集合时,N 可以很容易地为 8。
当你用 N=8 存储这 5 个数字时会发生什么?嗯,hash(1) % 8
,hash(2) % 8
等只是数字本身,但hash(88) % 8
为 0。因此,哈希表的数组最终包含88, 1, 2, NULL, NULL, 5, NULL, 7
。所以应该很容易弄清楚为什么迭代集合可能会给你88, 1, 2, 5, 7
.
当然 Python 并不能保证你每次都能得到这个订单。它猜测正确值的方式的微小变化N
可能意味着88
最终会出现在不同的地方(或最终与其他值之一发生冲突)。而且,事实上,在我的 Mac 上运行 CPython 3.7,我得到1, 2, 5, 7, 88
.0
同时,当您从大小为 11 的序列构建散列,然后将随机散列插入其中时,会发生什么?即使假设最简单的实现,并且假设没有冲突,您仍然不知道您将获得什么顺序。它会在 Python 解释器的一次运行中保持一致,但在您下次启动它时会有所不同。(除非您设置PYTHONHASHSEED
为0
,或其他一些 int 值。)这正是您所看到的。
当然,值得关注的是集合的实际实现方式,而不是猜测。但是,根据最简单的哈希表实现的假设,您会猜到(排除冲突和哈希表扩展)究竟会发生什么。