您需要在这里小心。您正在混合 2 个独立(但密切相关)的概念。
第一个概念是不变性。不可变对象一旦创建就无法更改。
第二个概念是hashability——从一个在其生命周期内不会改变的对象构造一个一致的整数值并定义一个一致的相等函数的能力1 .请注意,这些约束非常很重要(我们将看到)。
后一个概念决定了什么可以用作字典键(或集合项)。默认情况下,类实例具有明确定义的相等函数(两个对象相等,如果它们具有相同的id())。类实例也有一个在其生命周期内不会改变的整数值(它们的id())。因为id() 也作为hash() 返回值公开,所以类的实例(以及作为type 实例的类本身)默认是可散列的。
class Foo(object):
def __init__(self, a):
self.a = a
f1 = Foo(1)
f2 = Foo(1)
d = {
f1: 1,
f2: 2,
}
我们的字典中有 2 个独立的 Foo 实例。即使它们相同,它们也不相等,并且具有不同的哈希值。
f1 == f2 # False -- They do not have the same id()
hash(f1) == hash(f2) # False. By default, the hash() is the id()
好的,但并非所有事物都是可散列的——例如list 和 set 实例不可散列。在某些时候,引用相等不再那么有用了。例如我写:
d = {[1, 2, 3]: 6}
print(d[1, 2, 3])
我收到了KeyError。为什么?因为我的两个列表不是同一个列表——它们只是碰巧有相同的值。换句话说,它们相等,但它们没有引用相等。现在这才开始变得非常混乱。为了避免所有这些混淆,python 开发人员刚刚决定不将列表的id() 暴露给列表的hash()。相反,他们提出了一个TypeError 并带有(希望)更有帮助的错误消息。
hash([]) # TypeError: unhashable type: 'list'
请注意,平等被覆盖以做自然的事情,而不是通过id()进行比较:
l1 = [1]
l2 = [1]
l1 == l2 # True. Nice.
好的,到目前为止,我们基本上已经说过,要将某些内容放入字典中,我们需要具有良好行为的 __hash__ 和 __eq__ 方法,并且对象默认具有这些方法。一些对象选择删除它们以避免混淆情况。 不变性在哪里出现?
到目前为止,我们的世界包括能够将事物存储在表格中并通过对象的id()单独进行查找。这有时非常有用,但它仍然真的有限制。如果我只能依赖它们的id(),我将无法自然地在查找表中使用整数(如果我使用文字存储它,然后使用计算结果进行查找呢?)。幸运的是,我们生活在一个可以让我们解决这个问题的世界——不变性有助于构造一个hash() 值不绑定到对象的@ 987654344@ 并且在对象的生命周期内没有改变的危险。这可能非常有用,因为现在我可以做到:
d = {(1, 2, 3): 4}
d[(1, 2) + (3,)] # 4!
现在我使用的两个元组不是同一个元组(它们没有相同的id()),但它们相等,因为它们是不可变的,我们可以构造一个hash() 函数使用tuple 的内容而不是id()。这个超级好用!请注意,如果元组是可变的并且我们试图玩这个把戏,我们将(可能)违反hash() 不应在对象的生命周期内改变的条件。
1这里的一致意味着如果两个对象相等,那么它们也必须具有相同的哈希值。这是解决哈希冲突所必需的,我不会在这里详细讨论...