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)上给我相同的结果?
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)上给我相同的结果?
Answers:
hashlib
非加密使用的哈希函数会不会有点慢?
hash
95 ns,binascii.crc32
570 ns,hashlib.md5.digest()
1.42 us,murmur.string_hash
234 ns
hash
在每个python会话中使用新的随机生成的salt值。因此它将在python会话之间改变。
如文档中所述,内置的hash()函数并非旨在将生成的哈希存储在外部某个地方。它用于提供对象的哈希值,将其存储在字典中,依此类推。它也是特定于实现的(GAE使用Python的修改版)。查看:
>>> class Foo:
... pass
...
>>> a = Foo()
>>> b = Foo()
>>> hash(a), hash(b)
(-1210747828, -1210747892)
如您所见,它们是不同的,因为hash()使用对象的__hash__
方法而不是诸如SHA之类的“常规”哈希算法。
鉴于以上所述,合理的选择是使用hashlib模块。
int(hashlib.md5(repr(self)).hexdigest(), 16)
(假设self.__repr__
已定义为相同的iff对象相同)。如果32个字节太长,您当然可以通过在转换前对十六进制字符串进行切片来减小大小。
__repr__
足够唯一,则可以使用str.__hash__
(即hash(repr(self))
),因为dict不会将具有相同散列的不相等对象混合在一起。显然,这仅在对象琐碎到足以使repr可以表示身份的情况下才有效。
a
和b
,我如何使用hashlib模块来查看这些对象是相同的?
__hash__()
和__eq__()
方法。
回应绝对不足为奇:事实上
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.
大多数答案表明,这是因为平台不同,但还有更多。从以下文档中object.__hash__(self)
:
默认情况下,该
__hash__()
值str
,bytes
并且datetime
对象是“咸”不可预测的随机值。尽管它们在单个Python进程中保持不变,但在重复调用Python之间是不可预测的。目的是为了防止由于精心选择的输入而导致的拒绝服务,这些输入利用了dict插入的最坏情况的性能O(n²)复杂性。有关详细信息,请参见 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
另请参阅环境变量PYTHONHASHSEED
:
如果未设置,或设置为这个变量
random
,随机值被用于接种的散列str
,bytes
和datetime
对象。如果
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
哈希结果在32位和64位平台之间有所不同
如果两个平台上计算的哈希值相同,请考虑使用
def hash32(value):
return hash(value) & 0xffffffff
这是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
字符串的多项式哈希。1000000009
并且239
是任意质数。不太可能偶然发生碰撞。模块化算术不是很快,但是为了防止冲突,这比以幂为模更可靠2
。当然,很容易找到有意的碰撞。
mod=1000000009
def hash(s):
result=0
for c in s:
result = (result * 239 + ord(c)) % mod
return result % mod