为什么包含不可哈希类型的元组是不可哈希的?

5
例如,元组(1,[0,1,2])。从设计的角度来看,我理解为什么会这样;如果元组仍然可哈希,则使用元组将任何不可哈希的类型变成可哈希的类型就变得轻而易举了,从而破坏了哈希性的正确行为,因为你可以改变对象的值而不改变元组的哈希值。但是,如果元组不可哈希,则我不明白什么使一个对象可哈希--我认为它只需要实现__hash__(self),像元组一样。
根据我查看的其他答案和测试示例,似乎这样的对象是不可哈希的。合理的行为应该是tuple.__hash__()调用其组件对象的__hash__,但我不理解从实现的角度如何工作,例如,当它仍然是元组类型并且元组仍然定义了__hash__时,字典如何识别它为不可哈希的类型。

为什么包含不可哈希类型的元组是不可哈希的?答案就在问题中 ;) - Nir Alfasi
1
定义了一个__hash__函数并不意味着所有可能的元组都是可哈希的。就像定义了反函数并不意味着0有一个反元素一样。 - Julien
此外,你可以轻松地定义一个实现了 __hash__ 但是可变的自定义类。当然,如果你将该类的实例用作字典键或集合项,然后突变这些实例,可能会发生一些奇怪的事情。 ;) - PM 2Ring
2个回答

5

tuple 通过计算和组合其包含的值的哈希值来实现自己的哈希。当其中一个值的哈希失败时,它会让结果异常不受影响地传播。

不可哈希意味着在你上调用 hash() 将触发一个 TypeError;一种方法是不定义 __hash__ 方法,但如果在你的 __hash__ 方法中通过其他方式引发了 TypeError(或任何其他错误),也能起到同样的效果。

基本上,tuple 是一个可哈希类型(isinstance((), collections.abc.Hashable) 为 true,同样适用于 isinstance(([],), collections.abc.Hashable),因为它是对存在 __hash__ 的检查),但如果它存储不可哈希类型,则任何尝试计算哈希值的操作将在使用时引发异常,因此在这种情况下其行为类似于不可哈希类型。


我之前没有考虑到“不可哈希”意味着“在哈希时会生成错误”,而不是“永远无法被哈希”。现在非常清楚了,谢谢!实际上,这就解释了为什么 d = {(1,[]) : 0} 会引发 TypeError: unhashable type <list> -- 这个异常是由 tuple.__hash__() 而不是 dict.__hash__() 引发的! - colopop

2
我认为tuple.__hash__()会对元组中的每个项调用hash(item),然后将结果进行异或运算。如果其中一个项不可哈希,则会引发TypeError并向上传递给原始调用者。请注意保留html标签。

1
这不仅仅是异或运算(每个循环都基于长度进行乘法和一些奇怪的加法,以避免原始异或运算中的缺陷),但是是的,这是一般的想法。 - ShadowRanger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接