假设我写了一个类,但没有为它定义__hash__
。根据{{3}},__hash__(self)
默认为id(self)
(self
的内存地址)。
但是我没有在文档中看到这个值是如何使用的
因此,如果我的__hash__
只是return 1
,这将导致我的类的所有实例的哈希值相同,那么它们都会被压缩到相同的底层哈希桶中(我假设它是在C中实现的) )。但是,这并不意味着__hash__
的返回值被用作此基础哈希表中bin元素的键。
所以,我的问题是:__hash__
返回的值会发生什么变化?它是直接用作键,还是用作哈希表的键的哈希(或者在其上执行的其他一些计算的结果)?
如果重要,我在python2.7
编辑:为了澄清,我不是在询问如何处理哈希冲突。在python中,the documentation。相反,我问的是__hash__
的返回值如何转换为相应存储桶的内存地址(?)。
答案 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的情况,或者只是哈希函数实现的一个怪癖,可能有两个具有完全相同哈希码的对象。
有两种主要策略用于处理内存中单个机器的哈希表中的哈希冲突(用于分布式哈希表的不同技术等):
k
模N
的所有值都放在插槽k
的列表中。因此,如果哈希值发生冲突,那么这不是问题,因为具有相同哈希值的两个对象最终都在同一个列表中。k
模N
,则查看插槽k
。如果已满,则将一些公式应用于当前位置(可能只需添加1),然后查看下一个插槽。给定原始哈希值和目前为止的探测数量,您按照常规模式选择下一个插槽,并继续探测,直到找到一个打开的插槽。这种情况较少使用,因为如果你不小心你的实现,你可能会遇到群集问题,即在找到对象之前必须多次探测。Wikipedia更多地讨论了哈希表实现here。