python set 迭代返回元素顺序随机变化问题

记录一个由 python set 转 list 导致的一个元素顺序问题。

NLP 中经常需要把字符串列表(List[str])数据转换成数字列表(List[int])。这中间需要用到一个词汇表(List)把字符转换成相应的数字,然后还能将数字反向转换成字符串。

思路就是把所有出现的字符串放入一个 set 对象中,然后将 set 对象转换成 list 对象。

这里出现了一个意想不到的 python 特性,让我花费了很多时间来调试。

bug 复现

在不同的 python 会话中,运行以下代码,你会发现你得到不同输出

1
2
3
4
5
6
7
8
9
10
11
data = ['人之初', '性本善', '性相近', '习相远', '苟不教', '性乃迁']

def generate_a_set(data):
a_set = set()
for i in data:
a_set.add(i)

return a_set

a_set = generate_a_set(data)
print(list(a_set))

比如

第一次输出可能是:

1
['人之初', '性本善', '苟不教', '性乃迁', '习相远', '性相近']

第二次输出可能就是:

1
['苟不教', '习相远', '性相近', '性本善', '人之初', '性乃迁']

bug 产生的原理

这些问题在同一个 python 会话中,不会有问题,但是不同回话中,就会出现随机顺序的问题。

这是因为 set 背后的原理是 hash 结构,hash 结构需要一个 hash 函数计算 hash 值, python 中 string 的 hash 值和这个 string 对象的内存地址有关 (暂未找到具体证据佐证)。因此即使是同一个字符,在不同的 python 会话中,也会有不同的值。

在不同的 python 会话中,运行以下代码,你会发现你得到不同输出

1
2
3
4
5
6
7
data = ['人之初', '性本善', '性相近', '习相远', '苟不教', '性乃迁']

def print_hash(data):
for i in data:
print(hash(i))

print_hash(data)

比如:

1
2
3
4
5
6
-6354588181131181920
-3997371538074392634
-8077654492430038721
221811633827032250
-1695708399184889172
4668413180609257176

或者

1
2
3
4
5
6
3947240920277889685
3483355661880167026
8030610130069388583
-6821753054023619803
6445967894542493986
3223312263836510133

如何避免 bug

为了避免这种情况,可以通过对 set 对象中的元素排序,排序后的结果,按照元素排序,自然就没有随机顺序的问题了。