如何使用 d.items() 更改 for 循环中的所有字典键?

新手上路,请多包涵

我需要一些帮助来理解为什么这段代码没有按预期工作。

如果想要更改字典的键但保留值,他/她可能会使用:

 d[new_key] = d.pop[old_key]

我想修改所有键(并保留值)但下面的代码跳过某些行 - (“col2”) 保持不变。是不是因为字典是无序的,我一直在改变它的值?

我将如何在不创建新字典的情况下更改键并保留值?

 import time
import pprint

name_dict = {"col1": 973, "col2": "1452 29th Street",
             "col3": "Here is a value", "col4" : "Here is another value",
             "col5" : "NULL", "col6": "Scottsdale",
             "col7": "N/A", "col8" : "41.5946922",
             "col9": "Building", "col10" : "Commercial"}

for k, v in name_dict.items():
    print("This is the key: '%s' and this is the value '%s'\n" % (k, v) )
    new_key = input("Please enter a new key: ")
    name_dict[new_key] = name_dict.pop(k)
    time.sleep(4)

pprint.pprint(name_dict)

原文由 Robert 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 619
2 个回答

更改您正在迭代的对象从来都不是一个好主意。通常 dict 甚至在您尝试时抛出异常:

 name_dict = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}

for k, v in name_dict.items():
    name_dict.pop(k)

RuntimeError:字典在迭代期间改变了大小

但是,在您的情况下,您为每个删除的项目添加一个项目。这使它更加复杂。要了解正在发生的事情,您需要知道字典有点像稀疏表。例如像 {1: 1, 3: 3, 5: 5} 这样的字典可能看起来像这样(这在 Python 3.6 中发生了变化,对于 3.6 和更新版本,以下内容不再正确):

 hash    key    value
   -      -        -
   1      1        1
   -      -        -
   3      3        3
   -      -        -
   5      5        5
   -      -        -
   -      -        -
   -      -        -

这也是迭代的顺序。因此,在第一次迭代中,它将转到第二项(存储 1: 1 的位置)。假设您将密钥更改为 2 并删除密钥 1 字典将如下所示:

 hash    key    value
   -      -        -
   -      -        -
   2      2        1
   3      3        3
   -      -        -
   5      5        5
   -      -        -
   -      -        -
   -      -        -

但我们仍在第二行,因此下一次迭代将转到下一个“非空”条目,即 2: 1 。哎呀…

将字符串作为键甚至更复杂,因为字符串哈希是随机的(在每个会话的基础上),所以字典中的顺序是不可预测的。

在 3.6 中,内部布局发生了一些变化,但这里发生了类似的事情。

假设你有这个循环:

 name_dict = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}

for k, v in name_dict.items():
    # print(k, k+6, name_dict.__sizeof__())
    name_dict[k+6] = name_dict.pop(k)
    # print(name_dict)

初始布局是这样的:

 key   value
  1       1
  2       2
  3       3
  4       4
  5       5
  6       1

第一个循环删除 1 但添加 7 。因为字典在 3.6 中是有序的,所以插入了一个占位符,其中 1 曾经是:

 key   value
  -       -
  2       2
  3       3
  4       4
  5       5
  6       1
  7       2

这一直持续到您将 4 替换为 10

 key   value
  -       -
  -       -
  -       -
  -       -
  5       5
  6       1
  7       2
  8       3
  9       4
 10       5

但是,当您将 5 替换为 11 时,字典将需要增加其大小。然后会发生一些特别的事情:占位符被删除:

 key   value
  6       6
  7       1
  8       2
  9       3
 10       4
 11       5

因此,我们在上一次迭代中位于位置 5,现在我们更改第 6 行。但是第 6 行现在包含 11: 5 。哎呀…

永远不要改变你正在迭代的对象:不要在迭代过程中弄乱键(值是可以的)!

您可以改为保留一个“翻译表”(不知道这是否违反了您的“不创建新字典”的要求,但您需要某种存储才能使您的代码正常工作)并在循环后进行重命名:

 translate = {}
for k, v in name_dict.items():
    print("This is the key: '%s' and this is the value '%s'\n" % (k, v) )
    new_key = input("Please enter a new key: ")
    translate[k] = new_key
    time.sleep(4)

for old, new in translate.items():
    name_dict[new] = name_dict.pop(old)

原文由 MSeifert 发布,翻译遵循 CC BY-SA 3.0 许可协议

在 python3 dict.items() 中只是字典的一个视图。因为在迭代时不允许修改可迭代对象,所以在迭代 dict.items() 时不允许修改字典。您必须在迭代之前将 items() 复制到列表

for k, v in list(name_dict.items()):
    ...
    name_dict[new_key] = name_dict.pop(k)

这确实满足了您的“无新指令”要求,尽管该列表实际上包含您所有数据的完整副本。

你可以通过只复制键来稍微放松内存占用

for k in list(name_dict):
    v = name_dict.pop(k)
    ...
    name_dict[new_key] = v


编辑:归功于 Sven Krüger,他提出了旧密钥与新密钥冲突问题的可能性。在那种情况下你必须去

kv = list(name_dict.items())
name_dict.clear()
for k, v in kv :
    ...
    name_dict[new_key] = v

顺便说一句,有一个不创建新字典的用例,当前的字典可能会在其他地方引用。

原文由 stefan 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题