【问题标题】:Built in Python hash() function内置 Python hash() 函数
【发布时间】:2010-10-22 01:56:29
【问题描述】:

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)上给我相同的结果?

【问题讨论】:

  • 这是因为你的 winxp 是 32 位平台,而 google 的是 64 位

标签: python google-app-engine hash


【解决方案1】:

如文档中所述,内置 hash() 函数并非旨在将生成的哈希值存储在外部某处。它用于提供对象的哈希值,将它们存储在字典中等等。它也是特定于实现的(GAE 使用 Python 的修改版本)。签出:

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

如您所见,它们是不同的,因为 hash() 使用对象的 __hash__ 方法而不是“普通”散列算法,例如 SHA。

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

【讨论】:

  • 谢谢!我来到这里想知道为什么我总是会为相同的对象获得不同的哈希值,从而导致 dicts 出现意外行为(按哈希 + 类型索引而不是检查是否相等)。从 hashlib.md5 生成您自己的 int 散列的一种快速方法是 int(hashlib.md5(repr(self)).hexdigest(), 16)(假设 self.__repr__ 已被定义为相同 iff 对象相同)。如果 32 字节太长,当然可以通过在转换之前对十六进制字符串进行切片来减小大小。
  • 再想一想,如果__repr__ 足够独特,您可以只使用str.__hash__(即hash(repr(self))),因为dicts 不会将不相等的对象与相同的哈希混合。这仅在对象足够微不足道以至于 repr 可以代表身份时才有效。
  • 那么,在您的示例中,有两个对象 ab,我如何使用 hashlib 模块来查看对象是否相同?
【解决方案2】:

hashlib 用作hash() was designed to be used to

在字典查找过程中快速比较字典键

因此不保证它在所有 Python 实现中都是相同的。

【讨论】:

  • hashlib 中的哈希函数对于非加密用途来说是不是有点慢?
  • 与 Jenkins、Bernstein、FNV、MurmurHash 等通用哈希函数相比,它们实际上非常慢。如果您想创建自己的类似哈希表的结构,我建议您查看 utash.h uthash.sourceforge.net
  • 基准测试:hash 95 ns、binascii.crc32 570 ns、hashlib.md5.digest() 1.42 us、murmur.string_hash 234 ns
  • hash 在每个 python 会话中使用一个新的随机生成的盐值。所以它会在 python 会话之间改变。
  • 甚至不能保证在同一台机器上启动的 python 进程是相同的!尝试运行echo "print(hash('hej'))" | python3 - 几次,并注意每次输出的不同(python 3.6)。
【解决方案3】:

反应绝对不意外:事实上

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 中的“循环”乘法(无溢出)。

【讨论】:

    【解决方案4】:

    大多数答案表明这是因为平台不同,但还有更多。来自the documentation of object.__hash__(self)

    默认情况下,__hash__() 的值 strbytesdatetime 对象被“加盐”了一个不可预测的随机值。 尽管它们在单个 Python 进程中保持不变, 它们在 Python 的重复调用之间是不可预测的。

    这是为了防止拒绝服务 由利用最坏情况的精心选择的输入引起 字典插入的性能,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
    

    【讨论】:

    • 这仅适用于 Python 3.x,但由于 Python 3 既是现在也是未来,这是解决这个问题的唯一答案,+1。
    • 这是测试机器独立性的好方法 - 谢谢!
    【解决方案5】:

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

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

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

    【讨论】:

      【解决方案6】:

      据推测,AppEngine 正在使用 Python 的 64 位实现(-5768830964305142685 不适合 32 位),而您的 Python 实现是 32 位。您不能依赖对象哈希值在不同实现之间进行有意义的比较。

      【讨论】:

        【解决方案7】:

        这是 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
        

        【讨论】:

        • 你能分享一下这个散列函数的用途和原因吗?
        【解决方案8】:

        符号位呢?

        例如:

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

        小心使用:

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

        【讨论】:

          【解决方案9】:

          字符串的多项式哈希。 1000000009239 是任意素数。不太可能发生意外碰撞。模算术不是很快,但为了防止冲突,这比取模2 的幂更可靠。当然,故意找碰撞很容易。

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

          【讨论】:

            【解决方案10】:

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

            试试:

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

            【讨论】:

              【解决方案11】:

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

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

              【讨论】:

                猜你喜欢
                • 2018-10-25
                • 2013-09-16
                • 1970-01-01
                • 2019-12-20
                • 1970-01-01
                • 2023-03-21
                • 1970-01-01
                • 1970-01-01
                • 2018-05-23
                相关资源
                最近更新 更多