在Python中,我知道给定对象返回的值__hash__
在该对象的生命周期内应该是相同的。但是,出于好奇,如果不是这样的话会发生什么?这会造成什么样的破坏?
class BadIdea(object):
def __hash__(self):
return random.randint(0, 10000)
我知道__contains__
和__getitem__
会表现得很奇怪,因此,dicts和sets会表现得很奇怪。你也可能最终会成为孤儿" dict / set中的值。
还会发生什么?它可能会使解释器崩溃,还是破坏内部结构?
答案 0 :(得分:16)
你的主要问题确实在于dicts和sets。如果将对象插入到dict / set中,并且该对象的哈希值发生了变化,那么当您尝试检索该对象时,最终会在dict / set的基础数组中查找不同的点,从而找不到对象。这正是dict键应始终不可变的原因。
以下是一个小例子:假设我们将o
放入dict中,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的情况下,上面的数组的每个元素可以具有与其相关联的“值”,并且在单个点中可以存在多个元素(例如,hash collision)。此外,我们通常在决定放置元素的位置时采用模数大小的散列值。然而,无论所有这些细节如何,上面的例子仍然准确地传达了当对象的哈希码发生变化时可能出现的问题。
它可能导致解释器崩溃,还是内部结构损坏?
不,这不会发生。当我们说对象的哈希变化是“危险的”时,我们的意思是危险的,因为它基本上破坏了哈希的目的,并且如果不是不可能的话,也会使代码变得困难。我们并不意味着它可能导致崩溃。
答案 1 :(得分:8)
Github上有一篇很棒的帖子:What happens when you mess with hashing。 首先,您需要知道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 ...>
在这里,我们通过改变用于计算散列的参数来隐式改变a的散列。结果,在字典中找不到该对象,该字典使用散列来查找对象。
请注意,Python并不妨碍我这样做。我可以通过
__setattr__
加注AttributeError
来制作它,但即便如此,我也可以通过修改对象的__dict__
来强制改变它。当我们说Python是“同意成人”语言时,这意味着什么。
它不会使Python崩溃,但是dict / set和基于对象哈希的一切都会发生意外行为。