在对 Python 字典进行迭代时删除其中的项目可以吗?
- 2025-01-14 08:50:00
- admin 原创
- 124
问题描述:
假设我们有一个 Python 字典d
,并且我们像这样对其进行迭代:
for k, v in d.iteritems():
del d[f(k)] # remove some item
d[g(k)] = v # add a new item
(f
并且g
只是一些黑盒转换。)
换句话说,我们尝试d
在使用 对其进行迭代时添加/删除项目iteritems
。
这个定义清楚吗?你能提供一些参考资料来支持你的答案吗?
另请参阅如何避免“RuntimeError:迭代期间字典大小发生变化”错误?以了解如何避免该问题的单独问题。
解决方案 1:
亚历克斯·马特利 (Alex Martelli)在此对此进行了评论。
在循环遍历容器时更改容器(例如 dict)可能不安全。所以del d[f(k)]
可能不安全。如您所知,解决方法是使用d.copy().items()
(循环遍历容器的独立副本)而不是d.iteritems()
或d.items()
(使用相同的底层容器)。
可以修改字典现有索引处的值,但在新索引处插入值(例如d[g(k)] = v
)可能不起作用。
解决方案 2:
Python 文档页面(针对Python 2.7)上明确提到
iteritems()
在字典中添加或删除条目时使用可能会引发RuntimeError
或无法遍历所有条目。
对于Python 3来说也是如此。
iter(d)
对于、d.iterkeys()
和也是如此d.itervalues()
,我甚至会说对于 也是如此for k, v in d.items():
(我不记得具体是什么了for
,但如果实现调用 ,我不会感到惊讶iter(d)
)。
解决方案 3:
你不能这样做,至少对于 来说不能d.iteritems()
。我试过了,Python 失败了,
RuntimeError: dictionary changed size during iteration
如果您改用d.items()
,那么它就会起作用。
在 Python 3 中,d.items()
是字典的视图,就像d.iteritems()
在 Python 2 中一样。要在 Python 3 中执行此操作,请改用d.copy().items()
。这同样允许我们迭代字典的副本,以避免修改我们正在迭代的数据结构。
解决方案 4:
我有一个包含 Numpy 数组的大型字典,因此 @murgatroid99 建议的 dict.copy().keys() 方法是行不通的(尽管它有效)。相反,我只是将 keys_view 转换为列表,它工作得很好(在 Python 3.4 中):
for item in list(dict_d.keys()):
temp = dict_d.pop(item)
dict_d['some_key'] = 1 # Some value
我意识到这不会像上面的答案那样深入 Python 内部运作的哲学领域,但它确实为所述问题提供了实用的解决方案。
解决方案 5:
下面的代码表明这个定义不太明确:
def f(x):
return x
def g(x):
return x+1
def h(x):
return x+10
try:
d = {1:"a", 2:"b", 3:"c"}
for k, v in d.iteritems():
del d[f(k)]
d[g(k)] = v+"x"
print d
except Exception as e:
print "Exception:", e
try:
d = {1:"a", 2:"b", 3:"c"}
for k, v in d.iteritems():
del d[f(k)]
d[h(k)] = v+"x"
print d
except Exception as e:
print "Exception:", e
第一个例子调用 g(k),并抛出一个异常(字典在迭代过程中改变了大小)。
第二个示例调用 h(k) 并且没有引发异常,但是输出:
{21: 'axx', 22: 'bxx', 23: 'cxx'}
从代码来看,这似乎是错误的——我本来期望的是这样的:
{11: 'ax', 12: 'bx', 13: 'cx'}
解决方案 6:
对于 Python 3,你应该:
prefix = 'item_'
t = {'f1': 'ffw', 'f2': 'fca'}
t2 = dict()
for k,v in t.items():
t2[k] = prefix + v
或使用:
t2 = t1.copy()
您永远不应该修改原始字典,否则会导致混乱以及潜在的错误或运行时错误。除非您只是将新的键名附加到字典中。
解决方案 7:
这个问题询问如何使用迭代器(有趣的是,Python 2.iteritems
迭代器在 Python 3 中不再受支持)来删除或添加项目,并且它*的唯一正确答案一定是“否”,因为您可以在接受的答案中找到它。然而:大多数搜索者都试图找到解决方案,他们不会关心从技术上如何做到这一点,无论是迭代器还是递归,并且这个问题有一个解决方案:*
如果不使用附加(递归)函数,则无法循环更改字典。
因此,该问题应与具有可行解决方案的问题相链接:
如何删除深度嵌套字典中所选键所在的键值对?(=“删除”)
它还很有用,因为它展示了如何在运行时更改字典的项目:如何在深度嵌套的字典中所选键出现的位置用其值替换键:值对?(=“替换”)。
通过相同的递归方法,您也可以根据问题的要求添加项目。
由于我链接此问题的请求被拒绝,因此这里提供了可以从字典中删除项目的解决方案的副本。请参阅如何删除深度嵌套字典中所选键所在的键:值对?(=“删除”)以获取示例/致谢/注释。
import copy
def find_remove(this_dict, target_key, bln_overwrite_dict=False):
if not bln_overwrite_dict:
this_dict = copy.deepcopy(this_dict)
for key in this_dict:
# if the current value is a dict, dive into it
if isinstance(this_dict[key], dict):
if target_key in this_dict[key]:
this_dict[key].pop(target_key)
this_dict[key] = find_remove(this_dict[key], target_key)
return this_dict
dict_nested_new = find_remove(nested_dict, "sub_key2a")
诀窍
诀窍是在递归到达子级之前提前找出 target_key 是否在下一个子级中(= this_dict[key] = 当前字典迭代的值)。只有这样,您才能在迭代字典时删除子级的键值对。一旦您到达要删除的键所在的级别,然后尝试从那里删除它,您将收到错误:
RuntimeError: dictionary changed size during iteration
递归解决方案仅在下一个值的子级别上做出任何更改,因此避免了错误。
解决方案 8:
我遇到了同样的问题,并使用以下步骤解决了该问题。
即使在迭代过程中进行修改,Python 列表也可以进行迭代。因此对于以下代码,它将无限地打印 1。
for i in list:
list.append(1)
print 1
因此,通过协同使用 list 和 dict 就可以解决这个问题。
d_list=[]
d_dict = {}
for k in d_list:
if d_dict[k] is not -1:
d_dict[f(k)] = -1 # rather than deleting it mark it with -1 or other value to specify that it will be not considered further(deleted)
d_dict[g(k)] = v # add a new item
d_list.append(g(k))
解决方案 9:
今天我有一个类似的用例,但不是在循环开始时简单地将字典上的键具体化,而是希望对字典的更改影响字典的迭代,这是一个有序字典。
我最终构建了以下例程,它也可以在 jaraco.itertools 中找到:
def _mutable_iter(dict):
"""
Iterate over items in the dict, yielding the first one, but allowing
it to be mutated during the process.
>>> d = dict(a=1)
>>> it = _mutable_iter(d)
>>> next(it)
('a', 1)
>>> d
{}
>>> d.update(b=2)
>>> list(it)
[('b', 2)]
"""
while dict:
prev_key = next(iter(dict))
yield prev_key, dict.pop(prev_key)
文档字符串说明了用法。可以使用此函数代替d.iteritems()
上述函数来达到所需的效果。