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


295

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

例如:

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

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

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


1
一个带有非常有趣答案的相关问题:stackoverflow.com/questions/9023078/…
最大

一个人可以轻松尝试。如果失败,则不合法。
Trilarion 2015年

26
@Trilarion One可以很容易地尝试... 并且很容易学到任何有价值的东西。如果成功,这并不一定是合法的。边缘情况和意外的警告比比皆是。对于所有想成为Python的人来说,这个问题都是不平凡的。以“一个人可以轻松尝试!”的顺序挥舞双手 是无益的,并且与stackoverflow查询的好奇精神背道而驰。
Cecil Curry

在仔细阅读了max相关问题之后我必须同意。您可能只想细读这个令人不安的深度问题及其精心编写的答案。您的Pythonic头脑将被炸毁。
Cecil Curry

1
@CecilCurry如果我没有记错的话,那么在这里展示它之前先为自己测试一个想法是一种stackoverflow的精神。那就是我想传达的一切。抱歉,因此导致任何干扰。我也认为这是一个很好的问题,并未对此表示反对。我最喜欢Jochen Ritzel的回答。我认为当第二步删除要简单得多时,就不需要立即删除所有这些东西。我认为这应该是首选方式。
Trilarion '16

Answers:


305

编辑:

此答案不适用于Python3,并且会给出RuntimeError

RuntimeError:词典在迭代过程中更改了大小。

发生这种情况是因为mydict.keys()返回的是迭代器而不是列表。正如注释中所指出的那样,只需将其转换mydict.keys()为列表即可list(mydict.keys()),它应该可以工作。


控制台中的一个简单测试显示,在迭代字典时您无法修改字典:

>>> 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()方法:

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

53
请注意,在Python 3中,dict.items()返回一个迭代器(而dict.iteritems()消失了)。
Tim Lesher

83
要详细说明@TimLesher评论...在Python 3中将不起作用。–
max

99
为了详细说明@max的细节,如果将上面的代码转换为2to3,它将起作用。一个默认的固定器可以使毛的样子for k, v in list(mydict.items()):,工作正常在Python 3同为keys()成为list(keys())
沃尔特·蒙德

8
这行不通。我得到一个错误:RuntimeError: dictionary changed size during iteration
托马什Zato -恢复莫妮卡

14
正如Walter指出的,@TomášZato,对于python3,您需要使用for k in list(mydict.keys()): python3,因为它使keys()方法成为迭代器,并且不允许在迭代过程中删除dict项。通过添加list()调用,您可以将keys()迭代器转换为列表。因此,当您处于for循环的正文中时,您不再需要遍历字典本身。
Geoff Crompton

89

您也可以分两个步骤进行操作:

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)

11
@senderle:实际上是从2.7开始。
Jochen Ritzel 2011年

5
dict理解方法可复制词典;幸运的是,这些值至少不会被深深复制,而只是相互关联。但是,如果您有很多键,那可能很糟糕。因此,我更喜欢remove循环方法。
最大

1
您还可以组合以下步骤:for k in [k for k in mydict if k == val]: del mydict[k]
AXO

第一个解决方案是到目前为止该线程上唯一有效的大命令解决方案-因为它无法进行全长复制。
kxr

21

迭代时不能修改集合。那就是疯狂-最为明显的是,如果允许您删除和删除当前项目,则迭代器将必须继续(+1),下一次调用next将使您超出该范围(+2),因此您会最终跳过了一个元素(删除的元素后面的一个)。您有两种选择:

  • 复制所有键(或值,或两者,取决于您的需要),然后遍历这些键。您可以.keys()为此使用et al(在Python 3中,将生成的迭代器传递给list)。但是在空间上可能会非常浪费。
  • mydict照常进行迭代,将要保存的密钥保存在单独的collection中to_delete。当你完成迭代mydict,删除所有项目to_deletemydict。与第一种方法相比,可以节省一些(取决于删除的键数和剩余的键数)空间,但还需要多几行。

You can't modify a collection while iterating it.这仅适用于字典和朋​​友,但是您可以在迭代期间修改列表:L = [1,2,None,4,5] <\n> for n,x in enumerate(L): <\n\t> if x is None: del L[n]
Nils Lindemann

3
@Nils它不会引发异常,但是仍然不正确。观察:codepad.org/Yz7rjDVT-有关说明,请参见例如stackoverflow.com/q/6260089/395760

在这里找到我 仍然can't仅对字典和朋友正确,而shouldn't对列表是正确的。
尼尔斯·林德曼

20

而是遍历一个副本,例如items()

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

1
那没有多大意义-然后您不能del v直接使用,因此您已经为每个不再使用的v制作了一个副本,并且无论如何都必须通过键来访问这些项。dict.keys()是更好的选择。
jscs 2011年

2
@Josh:这完全取决于您需要使用多少v作为删除标准。
伊格纳西奥·巴斯克斯

3
在Python 3下,dict.items()返回迭代器而不是副本。有关布莱尔答案,请参阅评论。(不幸的是)它也采用了Python 2语义。
Cecil Curry

10

使用起来最干净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

1
@ AFP_555是的-我的目标是编写干净,并行的pythonic代码。如果您需要提高内存效率,我所知道的最好方法是迭代并构建要删除的键列表或要保存的项的新字典。使用Python是我的首要任务。对于大型数据集,我使用Go或Rust。
rsanden

9

您可以使用字典理解。

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


这是最Python的。
Yehosef

但是它创建了一个新的字典,而不是d就地修改。
阿里斯蒂德

9

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

使用python3进行测试,它可以正常工作,并且不会引发错误“ 字典在迭代期间更改大小 ”:

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 )
# {}

4

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

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第一个解决方案的副本(对没有完整副本的大字典有效)。虽然是“长期阅读”,但没有列表理解。但是,它可能会更快吗?
kxr

3

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

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

仅保证“开始”对于某些Python版本/实现是一致的。例如,Python 3.7新增功能

dict对象的插入顺序保留性质已声明是Python语言规范的正式组成部分。

这种方式避免了很多其他答案所暗示的dict副本,至少在Python 3中如此。


1

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

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.