关于字典:在迭代时修改Python字典

Modifying a Python dict while iterating over it

假设我们有一个python字典d,我们对它进行迭代,如下所示:

1
2
3
for k,v in d.iteritems():
    del d[f(k)] # remove some item
    d[g(k)] = v # add a new item

(fg只是一些黑盒转换。)

换句话说,我们尝试在使用iteritems迭代时向d添加/删除项。

这个定义清楚吗?你能提供一些参考资料来支持你的答案吗?

(很明显,如果它坏了,如何修复它,所以这不是我想要的角度。)


在python文档页面(对于python 2.7)中明确提到

Using iteritems() while adding or deleting entries in the dictionary may raise a RuntimeError or fail to iterate over all entries.

与Python3类似。

对于iter(d)d.iterkeys()d.itervalues()也是如此,我只想说它对for k, v in d.items():起作用(我不清楚for起什么作用,但如果这个实现称为iter(d)的话,我不会感到惊讶)。


亚历克斯·马泰利在这里说了算。

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

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


你不能这样做,至少用d.iteritems()。我试过了,但Python失败了

1
RuntimeError: dictionary changed size during iteration

如果您使用d.items(),那么它会起作用。

在python 3中,d.items()是一个进入字典的视图,就像python 2中的d.iteritems()。要在python 3中执行此操作,请使用d.copy().items()。这同样允许我们对字典的副本进行迭代,以避免修改我们正在迭代的数据结构。


下面的代码显示,这一点没有很好地定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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),不引发异常,但输出:

1
{21: 'axx', 22: 'bxx', 23: 'cxx'}

从代码上看,这似乎是错误的——我本以为会出现如下情况:

1
{11: 'ax', 12: 'bx', 13: 'cx'}


我有一个包含numpy数组的大字典,所以@murgatroid99建议的dict.copy().keys()方法是不可行的(尽管它有效)。相反,我只是将keys_视图转换为一个列表,它工作得很好(在Python3.4中):

1
2
3
for item in list(dict_d.keys()):
    temp = dict_d.pop(item)
    dict_d['some_key'] = 1  # Some value

我意识到这并不像上面的答案那样深入到Python内部工作的哲学领域,但它确实为所述问题提供了一个实际的解决方案。


我有同样的问题,我用下面的程序来解决这个问题。

即使在迭代过程中进行了修改,也可以对python列表进行迭代。所以对于下面的代码,它将无限地打印1。

1
2
3
for i in list:
   list.append(1)
   print 1

因此,协同使用list和dict可以解决这个问题。

1
2
3
4
5
6
7
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))


python 3您应该:

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

或使用:

1
t2 = t1.copy()

您不应该修改原始字典,它会导致混淆以及潜在的错误或运行时错误。除非用新的键名附加到字典中。


今天我有一个类似的用例,但是我不想在循环开始时简单地将字典上的键具体化,而是希望对dict进行更改以影响dict的迭代,这是一个有序的dict。

我最终建立了以下程序,也可以在jaraco.itertools中找到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)

docstring说明了用法。该功能可代替上述d.iteritems()功能,达到预期效果。