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

Posted

技术标签:

【中文标题】如何使用 d.items() 更改 for 循环中的所有字典键?【英文标题】:How to change all the dictionary keys in a for loop with d.items()? 【发布时间】:2018-02-03 06:43:59 【问题描述】:

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

如果一个人想改变字典的键但保留值,他/她可能会使用:

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)

【问题讨论】:

字典是无序的 - 完全正确 可以... new_dict = input('Enter new key for '.format(k)): v for k, v in name_dict.items()... ? @JonClements,不创建新字典 在迭代时修改往往会导致意外行为,你为什么不想要一个新的dict,是内存问题吗? @RomanPerekhrest 啊,好点......但仍然......除非它是纯粹的理论或绝对要求 - 这无疑是最简单的...... 【参考方案1】:

更改您正在迭代的对象绝不是一个好主意。通常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)

【讨论】:

这是一次非常有趣的学习经历。非常感谢参与帮助 Pyhtonista 同胞的时间。您介绍情况的方式非常完美且易于理解。【参考方案2】:

在 python3 中 dict.items() 只是 dict 的一个视图。因为在迭代时不允许修改可迭代对象,所以在迭代 dict.items() 时不允许修改 dict。 您必须在迭代之前将 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

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

【讨论】:

为什么不应该“允许”在迭代时修改可迭代对象?当您更改要迭代的可迭代对象时,它通常会产生(不需要的)副作用,但这并不意味着不允许这样做...... 我同意,从语言的角度来看是允许的。对我来说,从设计的角度来看是不允许的,除非你完全理解副作用并且你是唯一的维护者:-)。我猜在 python3 程序员的错误统计中,这是得分最高的人之一。【参考方案3】:

为了在您的工作内存中拥有一个不依赖于您的原始字典的可迭代对象,您可以使用方法fromkeys。现在可以使用旧值分配新键。但是您必须记住一件事:您不能将值分配给不是某个旧键的新键,而新键也是旧键集中的另一个键。

Old_Keys =  old_key_1, old_key_2, ..., old_key_n 

因此,您将与旧键相关的值分配给新键。

old_key_1  ->  new_key_1 not in Old_Keys  # Okay!
old_key_2  ->  new_key_2 == old_key_4     # Boom!... Error!...

使用以下内容时请注意这一点!

代码

D = 'key1': 'val1', 'key2': 'val2', 'key3': 'val3'

for key in D.fromkeys(D) :
    new_key = raw_input("Old Key: %s, New Key: " % key)
    D[new_key] = D.pop(key)

print D

控制台

Old Key: key1, New Key: abc

Old Key: key2, New Key: def

Old Key: key3, New Key: ghi

"abc": 'val1', "def": 'val2', "ghi": 'val3'

【讨论】:

fromkeys 实际上创建了一个新的字典,根据问题是不允许的。关键碰撞是一个好点。 我的理解是resulting字典不允许是新变量。

以上是关于如何使用 d.items() 更改 for 循环中的所有字典键?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 v-for 循环期间有条件地更改表格行样式?

SwiftUI如何更改for循环中的值

如何更改 - 使用 for 循环调用多个函数 - 使用管道调用类?

如何使用PL / SQL更改您在FOR循环IN子句中查询的表

如何更改以列表形式输入 for 循环的变量

如何在 FOR 循环中对 Python Pandas 列表中的元素执行字符串更改