如何在迭代字典时从字典中删除项目?

Posted

技术标签:

【中文标题】如何在迭代字典时从字典中删除项目?【英文标题】:How to delete items from a dictionary while iterating over it? 【发布时间】:2011-07-20 02:04:29 【问题描述】:

在 Python 中迭代字典时从字典中删除项目是否合法?

例如:

for k, v in mydict.iteritems():
   if k == val:
     del mydict[k]

这个想法是从字典中删除不满足特定条件的元素,而不是创建一个新字典,它是被迭代的字典的子集。

这是一个好的解决方案吗?有没有更优雅/高效的方法?

【问题讨论】:

一个相关问题的答案非常有趣:***.com/questions/9023078/…. 本可以轻松尝试。如果失败,则不合法。 @Trilarion 一个人可以很容易地尝试... 并且很容易学到任何有价值的东西。如果它成功了,它不一定是合法的。边缘案例和意想不到的警告比比皆是。这个问题对于所有想要成为 Python 的人来说都是非常重要的。挥手解雇,命令“一个人可以轻松尝试!”是无益的,与 *** 查询的求知精神背道而驰。 在仔细阅读max 的related question 之后,我必须同意。您可能只想仔细阅读这个令人不安的深入问题及其精心编写的答案。你的 Pythonic 头脑会被炸毁。 @CecilCurry 如果我没记错的话,在展示它之前为自己测试一个想法有点符合 *** 的精神。这就是我想要传达的全部内容。抱歉,如果因此而造成任何干扰。我也认为这是一个很好的问题,并没有否决它。我最喜欢Jochen Ritzel的回答。我认为在第二步中删除要简单得多时,不需要立即删除所有这些内容。在我看来,这应该是首选方式。 【参考方案1】:

编辑:

对于 Python3(或更高版本):

>>> mydict
'four': 4, 'three': 3, 'one': 1

>>> for k in list(mydict.keys()):
...     if mydict[k] == 3:
...         del mydict[k]
...
>>> mydict
'four': 4, 'one': 1

其余答案适用于 Python2 但不适用于 Python3 并引发RuntimeError

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

这是因为mydict.keys() 返回的是迭代器而不是列表。 正如 cmets 中所指出的,只需将 mydict.keys() 转换为 list(mydict.keys()) 的列表,它应该可以工作。


对于python2

控制台中的一个简单测试表明您无法在迭代字典时对其进行修改:

>>> mydict = 'one': 1, 'two': 2, 'three': 3, 'four': 4
>>> for k, v in mydict.iteritems():
...    if k == 'two':
...        del mydict[k]
...
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

正如 delnan 的回答中所述,当迭代器尝试移动到下一个条目时,删除条目会导致问题。相反,请使用 keys() 方法获取键列表并使用它:

>>> for k in mydict.keys():
...    if k == 'two':
...        del mydict[k]
...
>>> mydict
'four': 4, 'three': 3, 'one': 1

如果需要根据 items 值删除,请改用items() 方法:

>>> for k, v in mydict.items():
...     if v == 3:
...         del mydict[k]
...
>>> mydict
'four': 4, 'one': 1

【讨论】:

请注意,在 Python 3 中,dict.items() 返回一个迭代器(而 dict.iteritems() 消失了)。 详细说明@TimLesher 评论...这在 Python 3 中不起作用。 详细说明@max的阐述,如果你把上面的代码用2to3转换就行了。默认修复程序之一将使循环看起来像for k, v in list(mydict.items()):,它在 Python 3 中运行良好。keys() 变为 list(keys()) 也是如此。 这不起作用。我收到一个错误:RuntimeError: dictionary changed size during iteration @TomášZato 正如 Walter 指出的那样,对于 python3,您需要使用 for k in list(mydict.keys()):,因为 python3 使 keys() 方法成为迭代器,并且还不允许在迭代期间删除 dict 项。通过添加 list() 调用,您可以将 keys() 迭代器转换为列表。因此,当您在 for 循环的主体中时,您不再遍历字典本身。【参考方案2】:

您也可以分两步完成:

remove = [k for k in mydict if k == val]
for k in remove: del mydict[k]

我最喜欢的方法通常是制作一个新的字典:

# Python 2.7 and 3.x
mydict =  k:v for k,v in mydict.items() if k!=val 
# before Python 2.7
mydict = dict((k,v) for k,v in mydict.iteritems() if k!=val)

【讨论】:

@senderle:实际上是从 2.7 开始。 dict理解方法复制字典;幸运的是,这些值至少不会被深度复制,只是链接。不过,如果您有很多钥匙,那可能会很糟糕。出于这个原因,我更喜欢remove 循环方法。 也可以合并步骤:for k in [k for k in mydict if k == val]: del mydict[k] 到目前为止,第一个解决方案是该线程中对大字典唯一有效的解决方案 - 因为它不会制作完整长度的副本。【参考方案3】:

改为遍历一个副本,例如 items() 返回的那个:

for k, v in list(mydict.items()):

【讨论】:

这没有多大意义——那么你不能直接del v,所以你已经复制了每个你永远不会使用的 v 并且你必须访问无论如何,关键的项目。 dict.keys() 是更好的选择。 @Josh:这完全取决于您需要使用多少v 作为删除标准。 在 Python 3 下,dict.items() 返回一个迭代器而不是一个副本。请参阅 Blair 的 answer 的评论,它(遗憾地)也假设 Python 2 语义。【参考方案4】:

您不能在迭代集合时对其进行修改。这种方式很疯狂 - 最值得注意的是,如果您被允许删除并删除当前项目,则迭代器将不得不继续(+1)并且下一次调用 next 将带您超越(+2),所以你最终会跳过一个元素(你删除的元素后面的那个元素)。你有两个选择:

复制所有键(或值,或两者,取决于您的需要),然后遍历它们。您可以为此使用 .keys() 等(在 Python 3 中,将生成的迭代器传递给 list)。不过在空间方面可能会非常浪费。 像往常一样遍历mydict,将要删除的键保存在单独的集合to_delete 中。完成对 mydict 的迭代后,从 mydict 中删除 to_delete 中的所有项目。与第一种方法相比,节省了一些(取决于删除了多少键以及保留了多少键)空间,但也需要多几行。

【讨论】:

You can't modify a collection while iterating it. 这对 dicts 和朋友来说是正确的,但您可以在迭代期间修改列表:L = [1,2,None,4,5] &lt;\n&gt; for n,x in enumerate(L): &lt;\n\t&gt; if x is None: del L[n] @Nils 它没有抛出异常,但仍然不正确。观察:codepad.org/Yz7rjDVT——参见例如***.com/q/6260089/395760解释 找我。仍然can't 仅对 dict 和朋友是正确的,而对于列表应该是 shouldn't【参考方案5】:

使用 python3,迭代 dic.keys() 会引发字典大小错误。您可以使用这种替代方式:

用python3测试,效果很好,没有出现错误“dictionary changed size during iteration”:

my_dic =  1:10, 2:20, 3:30 
# Is important here to cast because ".keys()" method returns a dict_keys object.
key_list = list( my_dic.keys() )

# Iterate on the list:
for k in key_list:
    print(key_list)
    print(my_dic)
    del( my_dic[k] )


print( my_dic )
# 

【讨论】:

【参考方案6】:

使用 list(mydict) 最干净:

>>> mydict = 'one': 1, 'two': 2, 'three': 3, 'four': 4
>>> for k in list(mydict):
...     if k == 'three':
...         del mydict[k]
... 
>>> mydict
'four': 4, 'two': 2, 'one': 1

这对应于列表的并行结构:

>>> mylist = ['one', 'two', 'three', 'four']
>>> for k in list(mylist):                            # or mylist[:]
...     if k == 'three':
...         mylist.remove(k)
... 
>>> mylist
['one', 'two', 'four']

在 python2 和 python3 中都可以工作。

【讨论】:

这在您的数据集很大的情况下不好。这是在复制内存中的所有对象,对吧? @AFP_555 是的 - 我的目标是干净、并行、pythonic 代码。如果您需要内存效率,我所知道的最佳方法是迭代并构建要删除的键列表或要保存的新项目字典。使用 Python 时,美丽是我的首要任务;对于大型数据集,我使用 Go 或 Rust。【参考方案7】:

您可以使用字典理解。

d = k:d[k] for k in d if d[k] != val

【讨论】:

这是最Pythonic的。 但它会创建一个新字典,而不是就地修改d【参考方案8】:

您可以先构建一个要删除的键列表,然后遍历该列表以删除它们。

dict = 'one' : 1, 'two' : 2, 'three' : 3, 'four' : 4
delete = []
for k,v in dict.items():
    if v%2 == 1:
        delete.append(k)
for i in delete:
    del dict[i]

【讨论】:

它是@Ritzel 的第一个解决方案的副本(在没有完整副本的大字典上有效)。尽管没有列表理解的“长篇阅读”。然而,它可能会更快吗?【参考方案9】:

如果您要删除的项目始终位于 dict 迭代的“开始”,则有一种方法可能适合

while mydict:
    key, value = next(iter(mydict.items()))
    if should_delete(key, value):
       del mydict[key]
    else:
       break

“开始”仅保证对于某些 Python 版本/实现是一致的。例如来自What’s New In Python 3.7

dict 对象的插入顺序保存特性已被宣布为 Python 语言规范的官方部分。

这种方式避免了许多其他答案建议的 dict 副本,至少在 Python 3 中是这样。

【讨论】:

【参考方案10】:

我在 Python3 中尝试了上述解决方案,但在将对象存储在 dict 中时,这似乎是唯一对我有用的解决方案。基本上,您制作 dict() 的副本并在删除原始字典中的条目时对其进行迭代。

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])

【讨论】:

也可以使用for key, value in my_dict.copy().items(): 更简洁,避免在你的范围内添加不必要的变量

以上是关于如何在迭代字典时从字典中删除项目?的主要内容,如果未能解决你的问题,请参考以下文章

如何在特定参数内删除字典中的项目?

如何在迭代时从地图中删除?

如何在迭代时从 HashMap 中删除键? [复制]

Python:如何在迭代列表时从列表中删除元素而不跳过未来的迭代

如何在角度模板中迭代字典?

如何在 django 模板中迭代字典的列表值