内置的python hash()函数
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)相同的结果?
使用hashlib作为hash()
被devise用于 :
在字典查找过程中快速比较字典键
因此不能保证它在Python实现中是相同的。
正如文档中所述,内置的hash()函数并不是为了在外部存储结果散列而devise的。 它用于提供对象的散列值,将它们存储在字典中等等。 它也是实现特定的(GAE使用Python的修改版本)。 查看:
>>> class Foo: ... pass ... >>> a = Foo() >>> b = Foo() >>> hash(a), hash(b) (-1210747828, -1210747892)
正如你所看到的,它们是不同的,因为hash()使用对象的__hash__
方法而不是“正常的”散列algorithm,比如SHA。
鉴于以上情况,合理的select是使用hashlib模块。
事实上,这个回应绝对不会让人意外
In [1]: -5768830964305142685L & 0xffffffff Out[1]: 1934711907L
所以如果你想在ASCIIstring上得到可靠的响应,只需要将低32位作为uint
。 string的散列函数是32位安全的, 几乎是可移植的。
另一方面,你根本就不能依赖得到你没有明确定义__hash__
方法的任何对象的hash()
是不变的。
在ASCIIstring上它的工作原理就是因为散列是在构成string的单个字符上计算的,如下所示:
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)
的文档object.__hash__(self)
:
默认情况下,
str
,bytes
和datetime
对象的__hash__()
值是“salted”的,具有不可预知的随机值。 虽然它们在单独的Python过程中保持不变,但在重复调用Python之间却无法预测。这是为了防止由于精心挑选的input引起的拒绝服务,这些input利用了字典插入的最坏情况性能O(n2)的复杂性。 有关详细信息,请参阅http://www.ocert.org/advisories/ocert-2011-003.html 。
更改散列值会影响
dicts
,sets
和其他映射的迭代次序。 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
另见环境variablesPYTHONHASHSEED
:
如果此variables未设置或设置为
random
,则会使用随机值对str
,bytes
和datetime
对象的散列进行播种。如果
PYTHONHASHSEED
设置为整数值,则将其用作用于生成由散列随机化覆盖的types的hash()
的固定种子。它的目的是允许可重复散列,例如解释器本身的自测,或者允许一组python进程共享散列值。
整数必须是范围
[0, 4294967295]
的十进制数字。 指定值0
将禁用哈希随机化。
例如:
$ export PYTHONHASHSEED=0 $ python -c "print(hash('http://stackoverflow.com'))" -5843046192888932305 $ python -c "print(hash('http://stackoverflow.com'))" -5843046192888932305
散列结果在32位和64位平台之间变化
如果在两个平台上计算出来的散列应该是相同的
def hash32(value): return hash(value) & 0xffffffff
据猜测,AppEngine正在使用Python的64位实现(-5768830964305142685不适合32位),而您的Python实现是32位。 你不能依赖对象哈希在不同的实现之间进行有意义的比较。
这是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
什么标志位?
例如:
hex值0xADFE74A5
表示无符号2919134373
并签署了-1375832923
。 正确的值必须被签名(符号位= 1),但Python将其转换为无符号,并且我们有一个不正确的散列值从64位转换到32位。
小心使用:
def hash32(value): return hash(value) & 0xffffffff
string的多项式散列。 1000000009
和239
是任意素数。 偶然不小心碰撞。 模块化算术不是很快,但是为了防止碰撞,这比以2为模的方式更可靠。 当然,故意碰撞很容易。
mod=1000000009 def hash(s): result=0 for c in s: result = (result * 239 + ord(c)) % mod return result % mod
PYTHONHASHSEED的值可以用来初始化散列值。
尝试:
PYTHONHASHSEED python -c 'print(hash('http://stackoverflow.com'))'
它可能只是提供操作系统提供的function,而不是自己的algorithm。
正如其他评论所说,使用hashlib或编写自己的散列函数。