如何使用__hash__的返回值?

时间:2013-01-24 02:59:19

标签: python hash key

假设我写了一个类,但没有为它定义__hash__。根据{{​​3}},__hash__(self)默认为id(self)self的内存地址)。

但是我没有在文档中看到这个值是如何使用的 因此,如果我的__hash__只是return 1,这将导致我的类的所有实例的哈希值相同,那么它们都会被压缩到相同的底层哈希桶中(我假设它是在C中实现的) )。但是,这并不意味着__hash__的返回值被用作此基础哈希表中bin元素的键。
所以,我的问题是:__hash__返回的值会发生什么变化?它是直接用作键,还是用作哈希表的键的哈希(或者在其上执行的其他一些计算的结果)?

如果重要,我在python2.7

编辑:为了澄清,我不是在询问如何处理哈希冲突。在python中,the documentation。相反,我问的是__hash__的返回值如何转换为相应存储桶的内存地址(?)。

3 个答案:

答案 0 :(得分:2)

由于Python的哈希表的大小是2的幂,因此哈希值的较低位确定哈希表中的位置(或至少是初始探测的位置)。

表格大小为 n 的探测序列由下式给出:

def gen_probes(hashvalue, n):
    'Same sequence of probes used in the current dictionary design'
    mask = n - 1
    PERTURB_SHIFT = 5
    if hashvalue < 0:
        hashvalue = -hashvalue
    i = hashvalue & mask
    yield i
    perturb = hashvalue
    while True:
        i = (5 * i + perturb + 1) & 0xFFFFFFFFFFFFFFFF
        yield i & mask
        perturb >>= PERTURB_SHIFT

例如,字典:

d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}

存储为大小为8的数组,每个条目的格式为(hash, key, value)

entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

可以在此处找到Python词典中用于键插入的C源代码:http://hg.python.org/cpython/file/cd87afe18ff8/Objects/dictobject.c#l550

答案 1 :(得分:1)

当一个对象存储在字典中时,__hash__用于确定放置该对象的原始bin。但是,这并不意味着一个对象会与字典中的另一个对象混淆 - 他们仍然检查对象是否平等。它只是意味着字典在散列这种类型的对象时比其他字体慢一点。

答案 2 :(得分:0)

当然逻辑上(从使用哈希表的代码视图)对象本身就是关键。如果在哈希表中搜索键"foo",则无论哈希表中的其他任何对象具有与"foo"相同的哈希值,只有在键值之一时才会返回相应的值存储在哈希表中的对具有等于"foo"的密钥。

我不知道完全 Python的功能,但哈希表实现必须考虑哈希冲突。如果哈希表数组具有N个槽,那么如果插入N + 1个值(并且表没有先调整大小),则必定存在冲突。此外,正如您提到的__hash__始终返回1的情况,或者只是哈希函数实现的一个怪癖,可能有两个具有完全相同哈希码的对象。

有两种主要策略用于处理内存中单个机器的哈希表中的哈希冲突(用于分布式哈希表的不同技术等):

  1. 数组中的每个插槽都是一个列表(通常是链接列表),散布到kN的所有值都放在插槽k的列表中。因此,如果哈希值发生冲突,那么这不是问题,因为具有相同哈希值的两个对象最终都在同一个列表中。
  2. 某种探测方案。基本上,如果您要插入的对象的哈希值等于kN,则查看插槽k。如果已满,则将一些公式应用于当前位置(可能只需添加1),然后查看下一个插槽。给定原始哈希值和目前为止的探测数量,您按照常规模式选择下一个插槽,并继续探测,直到找到一个打开的插槽。这种情况较少使用,因为如果你不小心你的实现,你可能会遇到群集问题,即在找到对象之前必须多次探测。
  3. Wikipedia更多地讨论了哈希表实现here