正如许多人所指出的,Python 的hash不再一致(从版本 3.3 开始),因为PYTHONHASHSEED现在默认使用随机(为了解决安全问题,如这个优秀答案中所解释的)。
但是,我注意到某些对象的哈希值仍然是一致的(无论如何,从 Python 3.7 开始):包括int, float, tuple(x), frozenset(x)(只要x产生一致的哈希值)。例如:
assert hash(10) == 10
assert hash((10, 11)) == 3713074054246420356
assert hash(frozenset([(0, 1, 2), 3, (4, 5, 6)])) == -8046488914261726427
Run Code Online (Sandbox Code Playgroud)
这总是正确且有保证的吗?如果是这样,预计这种情况会持续下去吗?是PYTHONHASHSEED唯一适用于字符串和字节数组的哈希值加盐吗?
我为什么要问?
我有一个依赖哈希来记住我们是否已经看到给定字典(以任何顺序)的系统:{key: tuple(ints)}。在该系统中,键是文件名的集合,元组是 的子集os.stat_result,例如(size, mtime)与它们相关联。该系统用于根据检测差异做出更新/同步决策。
在我的应用程序中,我有大约 100K 个这样的字典,每个字典可以代表数千个文件及其状态,因此缓存的紧凑性很重要。
我可以容忍来自可能的哈希冲突的小误报率(对于 64 位哈希,< 10^-19)(另请参阅生日悖论)。
对于每个这样的字典“”,一个紧凑的表示如下fsd:
def fsd_hash(fsd: dict):
return hash(frozenset(fsd.items()))
Run Code Online (Sandbox Code Playgroud)
它非常快,并产生一个 int 来表示整个字典(具有顺序不变性)。如果fsd字典中的任何内容发生变化,则哈希值很可能会有所不同。
不幸的是, …
我理解为什么将可变对象放在字典中是危险的.但是,将所有列表/集转换为元组/ frozensets是昂贵的; 对于许多类型,根本没有容易获得的不可变版本.因此,有时可能需要直接对可变对象进行哈希处理,并采取适当的预防措施以确保永远不会修改相关对象.
在我开始为可变对象实现非常复杂的自定义散列函数之前,我想检查使用pickle.dumps散列函数是否有任何缺点- 无论是在性能还是碰撞方面还是其他任何方面.