有一点需要注意,mgilson的优秀回答中提到了,但是在现有的回答中没有明确提到:
小整数哈希值等于它们本身:
>>> [hash(x) for x in (1, 2, 3, 88)]
[1, 2, 3, 88]
字符串哈希到不可预测的值。事实上,从3.3版本开始,默认情况下它们是基于在启动时随机化的种子构建的(链接1)。因此,每个新的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值。)这正是您看到的。
当然,值得看一下
集合实际实现的方式,而不是猜测。但是,基于最简单的哈希表实现的假设所猜测的(除了冲突和哈希表扩展之外)正是发生的事情。
__hash__
函数的随机种子,使用相同字符串进行多次运行通常会以不同的顺序结束。 - mgilson