关于谷歌应用引擎:内置python hash()函数

Built in python hash() function

Windows XP,Python 2.5:P></

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

谷歌应用程序引擎(http:/ / / shell.appspot.com):P></

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

为什么is that?我怎么能在我的哈希函数相同的结果会给它在不同的平台(Windows,Linux,Mac)?P></


如文档中所述,内置hash()函数不是为在外部某个地方存储产生的哈希而设计的。它用于提供对象的散列值,将其存储在字典等中。它也是特定于实现的(GAE使用修改后的Python版本)。退房:

1
2
3
4
5
6
7
>>> class Foo:
...     pass
...
>>> a = Foo()
>>> b = Foo()
>>> hash(a), hash(b)
(-1210747828, -1210747892)

如您所见,它们是不同的,因为hash()使用对象的__hash__方法而不是"普通"的哈希算法,如sha。

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


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

quickly compare dictionary keys during a dictionary lookup

因此不能保证它在整个Python实现中都是相同的。


这一反应绝对不足为奇:事实上

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

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

另一方面,对于没有明确定义__hash__方法不变的任何对象,您完全不能依赖于获取其hash()

对于ASCII字符串,它的工作原理只是因为散列是根据构成字符串的单个字符计算的,如下所示:

1
2
3
4
5
6
7
8
9
10
11
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中的"循环"乘法(无溢出)。


大多数答案表明,这是因为不同的平台,但还有更多。根据object.__hash__(self)文件:

By default, the __hash__() values of str, bytes and
datetime objects are"salted" with an unpredictable random value.
Although they remain constant within an individual Python process,
they are not predictable between repeated invocations of Python.

This is intended to provide protection against a denial-of-service
caused by carefully-chosen inputs that exploit the worst case
performance of a dict insertion, O(n2) complexity. See
http://www.ocert.org/advisories/ocert-2011-003.html for details.

Changing hash values affects the iteration order of dicts, sets
and other mappings. Python has never made guarantees about this
ordering (and it typically varies between 32-bit and 64-bit builds).

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

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

而:

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

另见环境变量PYTHONHASHSEED

If this variable is not set or set to random, a random value is used
to seed the hashes of str, bytes and datetime objects.

If PYTHONHASHSEED is set to an integer value, it is used as a fixed
seed for generating the hash() of the types covered by the hash
randomization.

Its purpose is to allow repeatable hashing, such as for selftests for
the interpreter itself, or to allow a cluster of python processes to
share hash values.

The integer must be a decimal number in the range [0, 4294967295].
Specifying the value 0 will disable hash randomization.

例如:

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


哈希结果在32位和64位平台之间变化

如果两个平台上的计算散列值相同,则考虑使用

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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


大概,AppEngine使用的是64位的Python实现(5768830964305142685不适合32位),而您的Python实现是32位。不能依赖于对象散列在不同实现之间有意义地可比较。


符号位呢?

例如:

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

小心使用:

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


字符串的多项式哈希。1000000009239是任意素数。不可能发生意外碰撞。模块化算法不是很快,但为了防止碰撞,这比将其作为2的模幂更可靠。当然,故意发现碰撞是很容易的。

1
2
3
4
5
6
mod=1000000009
def hash(s):
    result=0
    for c in s:
        result = (result * 239 + ord(c)) % mod
    return result % mod


pythonhashseed的值可用于初始化哈希值。

尝试:

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

它可能只是请求操作系统提供的函数,而不是自己的算法。

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