为什么字典键必须是不可变的?

时间:2014-06-14 07:27:54

标签: python dictionary key immutability

为什么字典键必须是不可变的?我正在寻找一个简单明了的理由,为什么Python词典中的键具有这种限制。

2 个答案:

答案 0 :(得分:16)

在我的计算机上,有一个文件/etc/dictionaries-common/words包含大量英文单词:

>>> with open("/etc/dictionaries-common/words") as f:
...     words = [line.strip() for line in f]
... 
>>> "python" in words
True
>>> "BDFL" in words
False

让我们创建一个存储所有这些单词长度的字典:

>>> word_lengths = {w: len(w) for w in words}
>>> word_lengths["parrot"]
6

而且,只是为了踢,我们将改变原来的单词列表:

>>> from random import shuffle
>>> shuffle(words)
>>> words[:5]
["Willie's", 'Araceli', 'accessed', 'engagingly', 'hobnobs']
嗯,hobnobs。无论如何......现在我们已经与words混淆了一下,我们变得有点偏执(可能与我们渴望hobnobs的原因相同),我们想要检查所有的我们将word_lengths字典中的单词混合起来后仍然在words中:

>>> all(w in words for w in word_lengths)
True

嗯,我们到了那里,但在我的机器上花了三分多钟 - 至少有足够的时间吃几块美味的饼干。考虑到这一点,显而易见的原因是:我们已经......

>>> len(words)
99171

...需要检查近十万个单词,对于字典中的每个单词,Python必须搜索我们混合的单词列表,直到找到匹配项。它并不总是必须检查整个列表,但平均每次将是五万字(或列表的一半),总共50,000×100,000 = 5,000,000,000次测试。即使在这个奇迹般的技术时代,也有50亿美元。

只是为了绝对肯定(我通常不那么偏执;通常我只是困了),让我们检查相反的方式,并确保words中的所有内容仍然在word_lengths

>>> all(w in word_lengths for w in words)
True
嘿,什么?这次是十分之一秒!是什么赋予了?你吓坏我了,伙计......嘿,我的饼干在哪里?我刚才有他们,我很确定。

与列表不同,列表可以是任何旧的顺序(因此确保某些项目在那里意味着依次检查每个项目,直到我们找到它),字典更有效率。在聚会上可能不那么有趣,但嘿,让它掌控音乐,所有都是copacetic,你知道吗?

字典无情效率的秘诀在于,对于每个项目,字典根据其内容计算密钥的哈希(实际上只是一个整数),并使用它来将项目存储在内存中的特定位置。然后,当你去寻找项目时,它会再次计算密钥内容的哈希值,对自己说“好吧,"python",哈希到7036520087640895475 ...是的,我知道我必须在哪里已经把它,然后“,并直接到正确的记忆位置找到它。所以这一次,它只需要进行十万次检查,而不是五十亿次。

有点像把所有的CD整齐地放在架子上,而不是随机堆叠在扬声器顶部。我告诉你,字典知道它在哪里。

但是要为字典保持整合的能力付出代价。还记得当我说字典根据项目的内容计算哈希值时吗?那么,如果内容发生变化会发生什么?对于不存在问题的不可变对象 - 它们的内容不能更改 - 但是可变对象,根据定义,可以更改其内容,当它们执行时,它们的哈希值(如果他们甚至有一个)也会改变。这很酷,很明显,不是每个人都想放在一个盒子里,我明白了,但是如果哈希发生了变化,字典就无法解决它所放置的问题。

就像Joy Division将他们的名字改为New Order一样,现在你已经不知道你把蓝色星期一的12“混音放在哪里了。它只是不会起作用。

所以,字典有一个规则:如果你想成为一个关键,不要改变

答案 1 :(得分:4)

作为Fredrik Lundh states here

  

字典的哈希表实现使用哈希值   从键值计算找到键。如果钥匙是   可变对象,它的值可能会改变,因此它的哈希也可以   更改。但是,既然改变关键对象的人无法分辨它   被用作字典键,它无法移动条目   词典。然后,当你试图查找同一个对象时   字典将无法找到,因为它的哈希值不同。如果   你试图查找它不会被找到的旧值,   因为在该哈希箱中找到的对象的值将是   不同。