Python为较小的dict替换键有效,但为较大的dict引发KeyError



代码1:

kv = {'a': 'A', 'b': 'B', 'c': 'C'}
d = {'a': 1, 'b': 2, 'c': 3}
for key in d.keys():
d[kv[key]] = d.pop(key)
print(d)

输出:

{'A': 1, 'B': 2, 'C': 3}

上面的例子很好
但是,当我们增加字典中的元素数量时,
会引发KeyError,如下例所示代码2:

kv = {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
for key in d.keys():
d[kv[key]] = d.pop(key)
print(d)

输出:

Traceback (most recent call last):
File "C:UsersuserOneDriveDocumentsVSCode_Pythonnew.py", line 11, in <module>
d[kv[key]] = d.pop(key)
KeyError: 'A'

发生这种情况的实际原因是Python中字典的实现。这篇文章描述得很好。

本质上,Python计算密钥的散列,并应用掩码来查找存储值的槽。

对于一个小dict,你有8个插槽,所以确定你把值放在哪个插槽的函数是:

def find_slot(key):
return hash(key) & 7

你会注意到原来的dict:中没有冲突

list(map(find_slot, ('a', 'b', 'c', 'd')))
>>>[5, 0, 6, 1]

但当添加'A'密钥时,会发生冲突:

list(map(find_slot, ('a', 'b', 'c', 'd', 'A')))
>>>[5, 0, 6, 1, 5]

'a''A'提供了相同的槽,因此字典必须扩展以具有更多的槽。

在字典展开之前,密钥散列存储在缓存中。当它扩展时,必须续订缓存。你可以在一些打印报表中看到这种情况:

from sys import getsizeof
kv = {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
for key in d.keys():
print(d)
print(getsizeof(d))
print(d.keys(), key)
d[kv[key]] = d.pop(key)
print(d)

这就是原则。不确定细节是否100%正确,但如果有人有任何意见,很乐意修复。

最简单的答案可以在Python文档中找到;字典视图对象":https://docs.python.org/3/library/stdtypes.html#dict-视图:

在字典中添加或删除条目时迭代视图可能会引发RuntimeError或无法迭代所有条目。

它说它可能无法迭代所有条目。在长度为4的dict的示例中,似乎对前3个条目(就在依次弹出每个条目之前(迭代keys()字典视图对象是有效的(即,其行为与不在循环中更改字典时的行为相同(,然后在到达关键字"d"之前到达新设置的关键字"a"。换句话说,迭代这个视图确实失败了,无法迭代长度为4的字典的所有条目。

关于为什么它在长度为3的字典中没有失败的问题,也许可以参考实现细节来回答,这些细节使我们能够理解语言内部是如何产生这种绘制行为的,但最终,答案是Python语言规范不要求您的代码对于任何长度的字典上的视图都失败,而且恰好它对于长度为3的dict也不会失败。

请参阅@kcsquare的评论和其他人的回答,以深入了解具体实现的细节,了解/a当前Python语言实现的任意故障点。

最新更新