如果对象的__hash__发生变化会发生什么?

Ric*_*eur 24 python hashcode

在Python中,我知道__hash__给定对象的值返回应该与该对象的生命周期相同.但是,出于好奇,如果不是,会发生什么?这会造成什么样的破坏?

class BadIdea(object):
  def __hash__(self):
    return random.randint(0, 10000)
Run Code Online (Sandbox Code Playgroud)

我知道__contains__并且__getitem__会表现得很奇怪,因此,dicts和sets会表现得很奇怪.你也可能在dict/set中得到"孤立"值.

还有什么可能发生的?它可能会使解释器崩溃,还是破坏内部结构?

ars*_*jii 16

你的主要问题确实是dicts和sets.如果将对象插入到dict/set中,并且该对象的散列发生更改,那么当您尝试检索该对象时,您将最终查找dict/set的基础数组中的不同位置,因此无法找到该对象.这正是dict键应始终不可变的原因.

这是一个小例子:让我们说我们放入o一个字典,并且o最初的哈希是3.我们会做这样的事情(稍微简化但是得到了重点):

Hash table:

  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
|   |   |   | o |   |   |   |   |
+---+---+---+---+---+---+---+---+
              ^
              we put o here, since it hashed to 3

现在让我们说o改变的哈希值6.如果我们想要o从字典中检索,我们会看现场6,但那里什么都没有!在查询数据结构时,这将导致误报.实际上,在dict的情况下,上面的数组的每个元素可以具有与其相关联的"值",并且在单个点中可以存在多个元素(例如,散列冲突).此外,我们通常在决定放置元素的位置时采用模数大小的散列值.然而,无论所有这些细节如何,上面的例子仍然准确地传达了当对象的哈希码发生变化时可能出现的问题.

它可能会使解释器崩溃,还是破坏内部结构?

不,这不会发生.当我们说对象的哈希变化是"危险的"时,我们的意思是危险的,因为它基本上破坏了哈希的目的,并且如果不是不可能的话,也会使代码变得困难.从它可能导致崩溃的意义上讲,我们并不意味着危险.


Max*_*ant 8

在Github上有一篇很棒的帖子:当你搞乱哈希时会发生什么.首先,您需要知道Python期望(引自文章):

  • 对象的哈希值不会在对象的生命周期内发生变化(换句话说,可哈希的对象应该是不可变的).

  • a == b暗示hash(a) == hash(b)(请注意,在哈希冲突的情况下反向可能不成立).

下面是代码示例,它显示了一个变量哈希的问题,但是类的示例略有不同,但这个想法保持不变:

>>> class Bad(object): 
...     def __init__(self, arg): 
...         self.arg = arg 
...     def __hash__(self): 
...         return hash(self.arg) 
... 
>>> Bad(1) 
<__main__.Bad object at ...> 
>>> hash(Bad(1)) 
1 
>>> a = Bad(1) 
>>> b = {a:1} 
>>> a.arg = 2 
>>> hash(a) 
2 
>>> b[a] 
Traceback (most recent call last):
...
KeyError: <__main__.Bad object at ...>
Run Code Online (Sandbox Code Playgroud)

在这里,我们通过改变用于计算哈希的参数来隐式地改变了a的哈希.结果,在字典中找不到该对象,该字典使用散列来查找对象.

请注意,Python并不妨碍我这样做.如果我愿意,我可以通过__setattr__加注来制作它AttributeError,但即便如此,我也可以通过修改对象来强制改变它__dict__.当我们说Python是"同意成人"语言时,这意味着什么.

它不会使Python崩溃,但是dict/set和基于对象哈希的一切都会发生意外行为.