内置Python hash()函数


82

Windows XP,Python 2.5:

hash('http://stackoverflow.com') Result: 1934711907

Google App Engine(http://shell.appspot.com/):

hash('http://stackoverflow.com') Result: -5768830964305142685

这是为什么?我如何拥有一个散列函数,以便在不同平台(Windows,Linux,Mac)上给我相同的结果?


14
这是由于您的winxp是32位平台,而Google的winxp是64位
Tzury Bar Yochay 2011年

Answers:


56

使用hashlib作为hash() 被设计用于

在字典查找期间快速比较字典键

因此,不保证在所有Python实现中都一样。


5
hashlib非加密使用的哈希函数会不会有点慢?
布兰登·罗兹

8
与诸如Jenkins,Bernstein,FNV,MurmurHash等许多通用哈希函数相比,它们实际上非常慢。如果您要创建自己的类似于哈希表的结构,建议您查看uthash.h uthash.sourceforge.net
lericson 2011年

45
基准:hash95 ns,binascii.crc32570 ns,hashlib.md5.digest()1.42 us,murmur.string_hash234 ns
temoto 2012年

hash在每个python会话中使用新的随机生成的salt值。因此它将在python会话之间改变。
滚刀

89

如文档中所述,内置的hash()函数并非旨在将生成的哈希存储在外部某个地方。它用于提供对象的哈希值,将其存储在字典中,依此类推。它也是特定于实现的(GAE使用Python的修改版)。查看:

>>> class Foo:
...     pass
... 
>>> a = Foo()
>>> b = Foo()
>>> hash(a), hash(b)
(-1210747828, -1210747892)

如您所见,它们是不同的,因为hash()使用对象的__hash__方法而不是诸如SHA之类的“常规”哈希算法。

鉴于以上所述,合理的选择是使用hashlib模块。


谢谢!我来到这里想知道为什么我总是会为相同的对象获得不同的哈希值,从而导致dict发生意外行为(按哈希值+类型索引,而不是检查相等性)。从hashlib.md5生成自己的int哈希的一种快速方法是int(hashlib.md5(repr(self)).hexdigest(), 16)(假设self.__repr__已定义为相同的iff对象相同)。如果32个字节太长,您当然可以通过在转换前对十六进制字符串进行切片来减小大小。
艾伦·梅

1
再考虑一下,如果__repr__足够唯一,则可以使用str.__hash__(即hash(repr(self))),因为dict不会将具有相同散列的不相等对象混合在一起。显然,这仅在对象琐碎到足以使repr可以表示身份的情况下才有效。
艾伦·梅

因此,在您的示例中有两个对象ab,我如何使用hashlib模块来查看这些对象是相同的?
加勒特2014年


32

回应绝对不足为奇:事实上

In [1]: -5768830964305142685L & 0xffffffff
Out[1]: 1934711907L

因此,如果您想获得ASCII字符串的可靠响应,只需将低32位作为即可uint。字符串的哈希函数是32位安全的,几乎可以移植。

另一方面,您完全不能依赖于hash()尚未将__hash__方法明确定义为不变的任何对象。

对于ASCII字符串,它起作用是因为哈希是根据构成字符串的单个字符计算出来的,如下所示:

class string:
    def __hash__(self):
        if not self:
            return 0 # empty
        value = ord(self[0]) << 7
        for char in self:
            value = c_mul(1000003, value) ^ ord(char)
        value = value ^ len(self)
        if value == -1:
            value = -2
        return value

其中c_mul函数是“环状”乘法(不溢出),如C.


18

大多数答案表明,这是因为平台不同,但还有更多。从以下文档中object.__hash__(self)

默认情况下,该__hash__()strbytes并且 datetime对象是“咸”不可预测的随机值。尽管它们在单个Python进程中保持不变,但在重复调用Python之间是不可预测的。

目的是为了防止由于精心选择的输入而导致的拒绝服务,这些输入利用了dict插入的最坏情况的性能O(n²)复杂性。有关详细信息,请参见 http://www.ocert.org/advisories/ocert-2011-003.html

更改哈希值影响的迭代顺序dictssets 和其他的映射。Python从未保证过这种顺序(通常在32位和64位版本之间有所不同)。

即使在同一台机器上运行,也会因调用而产生不同的结果:

$ python -c "print(hash('http://stackoverflow.com'))"
-3455286212422042986
$ python -c "print(hash('http://stackoverflow.com'))"
-6940441840934557333

而:

$ python -c "print(hash((1,2,3)))"
2528502973977326415
$ python -c "print(hash((1,2,3)))"
2528502973977326415

另请参阅环境变量PYTHONHASHSEED

如果未设置,或设置为这个变量random,随机值被用于接种的散列strbytesdatetime对象。

如果PYTHONHASHSEED将其设置为整数值,则它将用作生成hash()散列随机化所覆盖类型的的固定种子。

它的目的是允许可重复的哈希,例如用于解释程序本身的自测,或允许一组python进程共享哈希值。

该整数必须是范围内的十进制数[0, 4294967295]。指定该值0将禁用哈希随机化。

例如:

$ export PYTHONHASHSEED=0                            
$ python -c "print(hash('http://stackoverflow.com'))"
-5843046192888932305
$ python -c "print(hash('http://stackoverflow.com'))"
-5843046192888932305

3
这仅适用于Python 3.x,但由于Python 3是现在和将来,因此这是解决此问题的唯一答案+1。
Alexander Huszagh 2015年

8

哈希结果在32位和64位平台之间有所不同

如果两个平台上计算的哈希值相同,请考虑使用

def hash32(value):
    return hash(value) & 0xffffffff

6

猜测一下,AppEngine使用的是Python的64位实现(-5768830964305142685不能容纳32位),而您的Python实现是32位。您不能依靠对象散列在不同实现之间具有有意义的可比性。


6

这是Google在python 2.5的生产中使用的哈希函数:

def c_mul(a, b):
  return eval(hex((long(a) * b) & (2**64 - 1))[:-1])

def py25hash(self):
  if not self:
    return 0 # empty
  value = ord(self[0]) << 7
  for char in self:
    value = c_mul(1000003, value) ^ ord(char)
  value = value ^ len(self)
  if value == -1:
    value = -2
  if value >= 2**63:
    value -= 2**64
  return value

7
您是否可以共享有关此哈希函数的用途以及原因的任何上下文?
amcnabb 2012年

5

那标志位呢?

例如:

十六进制值0xADFE74A5表示无符号2919134373和有符号-1375832923。当前值必须是带符号的(符号位= 1),但python会将其转换为无符号,并且在从64位转换为32位后,我们的哈希值不正确。

请谨慎使用:

def hash32(value):
    return hash(value) & 0xffffffff

3

字符串的多项式哈希。1000000009并且239是任意质数。不太可能偶然发生碰撞。模块化算术不是很快,但是为了防止冲突,这比以幂为模更可靠2。当然,很容易找到有意的碰撞。

mod=1000000009
def hash(s):
    result=0
    for c in s:
        result = (result * 239 + ord(c)) % mod
    return result % mod

2

PYTHONHASHSEED的值可能用于初始化哈希值。

尝试:

PYTHONHASHSEED python -c 'print(hash('http://stackoverflow.com'))'

-3

它可能只是问操作系统提供的功能,而不是它自己的算法。

正如其他评论所说,使用hashlib或编写自己的哈希函数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.