在迭代时修改Python字典


87

假设我们有一个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

这个定义好吗?您能否提供一些参考来支持您的答案?

(很明显,如果损坏了该如何解决,所以这不是我所追求的角度。)




我尝试这样做,看来,如果您保留初始dict大小不变-例如,替换任何键/值而不是删除它们,则此代码将不会引发异常
Artsiom Rudzenka 2011年

我不同意对于搜索该主题的每个人(包括我自己),“如果损坏了该如何解决”,这很明显,我希望被接受的答案至少能解决这个问题。
亚历克斯·彼得斯

Answers:


53

在python文档页面(针对Python 2.7)上明确提到了

使用iteritems()而添加或删除字典条目可能会产生一种RuntimeError或无法遍历所有条目。

对于Python 3同样如此。

这同样适用于iter(d)d.iterkeys()d.itervalues(),我会去远的话说,它为for k, v in d.items():(我不记得到底是什么for做的,但我不会感到惊讶,如果实现调用iter(d))。


48
我会为了社区而感到尴尬,说我使用了非常简短的代码段。认为由于没有得到RuntimeError,所以我认为一切都很好。确实有一段时间了。肛门保持性单元测试给了我赞许,并且在发布时它甚至运行良好。然后,我开始变得奇怪。发生的事情是字典中的项目被跳过了,因此并不是字典中的所有项目都被扫描了。孩子们,从我一生中犯的错误中吸取教训,然后说不!;)
Alan Cabrera 2015年

3
如果要更改当前键的值(但不添加或删除任何键?),我会遇到问题吗?我想这不会造成任何问题,但我想知道!
Gershom 2015年

@GershomMaes我一无所知,但是如果您的循环主体使用该值并且不希望其改变,您可能仍会遇到雷区。
拉斐尔·圣皮埃尔

3
d.items()在python 2.7中应该是安全的(游戏随python 3改变了),因为它实质上是的副本d,因此您无需修改​​要迭代的内容。
保罗·普赖斯

知道这是否对viewitems()
jlh

50

亚历克斯玛特利(Alex Martelli)在这里对此有所考虑。

在容器上循环时,更改容器(例如dict)可能并不安全。因此del d[f(k)]可能并不安全。如您所知,解决方法是使用d.items()(循环遍历容器的独立副本)而不是d.iteritems()(使用相同的基础容器)。

可以在dict的现有索引处修改值,但是在新索引处插入值(例如d[g(k)]=v)可能不起作用。


3
我认为这是我的主要答案。很多用例都会有一个过程来插入事物,而另一个过程则是清理/删除事物,因此使用d.items()的建议行之有效。Python 3的警告不
容忍

4
有关Python 3警告的更多信息,请参见PEP 469,其中列举了上述Python 2 dict方法的语义等效项。
Lionel Brooks 2014年

1
“可以在dict的现有索引处修改值” –您对此有参考吗?
乔纳森·莱因哈特

1
@JonathonReinhart:不,我对此没有参考,但是我认为这在Python中是非常标准的。例如,Alex Martelli是一名Python核心开发人员,并在此处演示了其用法
unutbu

27

至少不能使用来做到这一点d.iteritems()。我尝试了,Python失败了

RuntimeError: dictionary changed size during iteration

如果您改为使用 d.items(),则可以。

在Python 3中,d.items()就像d.iteritems()在Python 2中一样,是字典的视图。要在Python 3中做到这一点,请使用d.copy().items()。同样,这将使我们能够迭代字典的副本,从而避免修改正在迭代的数据结构。


2
我在答案中添加了Python 3。
murgatroid99 2013年

2
仅供参考,2to3Py2d.items()到Py3的字面翻译(例如由所使用)为list(d.items()),尽管d.copy().items()效率可能相当。
索伦Løvborg

2
如果dict对象很大,则d.copy()。items()有效吗?
蜻蜓

11

我有一个包含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内部工作的哲学领域,但是它确实为所述问题提供了一种实用的解决方案。


6

以下代码显示此定义不明确:

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'}

我能理解您为什么会期望,{11: 'ax', 12: 'bx', 13: 'cx'}但是21,22,23应该为您提供实际发生情况的线索:您的循环遍历了项目1、2、3、11、12、13,但没有设法获得第二个项目一轮新项目,因为它们插入了您已经遍历的项目的前面。更改h()为return x+5,您将得到另一个x:'axxx'等或“ x + 3”,并且得到了壮观'axxxxx'
Duncan

是的,我很怕我的错误-我的预期输出是{11: 'ax', 12: 'bx', 13: 'cx'}您所说的,所以我将更新有关它的信息。无论哪种方式,这显然都不是明确定义的行为。
战斗

1

我遇到了同样的问题,并使用以下步骤解决了此问题。

即使您在迭代过程中进行修改,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))

我不确定在迭代过程中修改列表是否安全(尽管在某些情况下可能可行)。例如,请参阅此问题...
罗马

@Roman如果要删除列表中的元素,则可以以相反的顺序安全地对其进行迭代,因为按照正常顺序,下一个元素的索引会在删除后更改。请参阅此示例。
mbomb007 '18

1

Python 3您应该:

prefix = 'item_'
t = {'f1': 'ffw', 'f2': 'fca'}
t2 = dict() 
for k,v in t.items():
    t2[k] = prefix + v

或使用:

t2 = t1.copy()

您永远不要修改原始字典,否则会导致混乱以及潜在的错误或RunTimeErrors。除非您只是使用新的键名附加到字典中。


0

今天,我有一个类似的用例,但我不希望在循环开始时简单地在字典上实现键,而是要更改字典以影响字典的迭代,这是一个有序的字典。

我最终构建了以下例程,也可以在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()上面的功能使用此功能,以达到理想的效果。

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.