在迭代它时修改Python字典

NPE*_*NPE 75 python dictionary

假设我们有一个Python字典d,我们就像这样迭代它:

for k,v in d.iteritems():
    del d[f(k)] # remove some item
    d[g(k)] = v # add a new item
Run Code Online (Sandbox Code Playgroud)

(f并且g只是一些黑盒转换.)

换句话说,我们尝试添加/删除项目,d同时使用迭代它iteritems.

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

(如果它被破坏了,如何解决这个问题非常明显,所以这不是我追求的角度.)

Rap*_*rre 49

它在Python文档页面(对于Python 2.7)中明确提到过

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

同样适用于Python 3.

同样适用于iter(d),d.iterkeys()而且d.itervalues(),我会尽力说它的确如此for k, v in d.items():(我不记得具体是什么for,但如果实现调用,我不会感到惊讶iter(d)).

  • 我会通过声明我使用了非常代码片段来为社区的缘故让自己难堪.认为既然我没有得到RuntimeError,我认为一切都很好.有一段时间了.Anally保持性单元测试给了我竖起大拇指,它在发布时甚至运行良好.然后,我开始变得奇怪的行为.发生的事情是字典中的项目被跳过,因此字典中的所有项目都没有被扫描.孩子们,从我在生活中犯下的错误中吸取教训,然后说不!;) (32认同)
  • 如果我正在更改当前键的值(但不添加或删除任何键,我可以遇到问题吗?)我会想到这不应该导致任何问题,但我想知道! (2认同)
  • `d.items()`在Python 2.7中应该是安全的(游戏随Python 3而改变),因为它实际上是`d`的副本,因此你不会修改你正在迭代的内容. (2认同)

unu*_*tbu 44

Alex Martelli在这里重视这一点.

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

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

  • 有关Python 3警告的更多信息可以在[PEP 469](http://legacy.python.org/dev/peps/pep-0469/#lists-as-mutable-snapshots)中找到,其中上述语义等价物枚举Python 2 dict方法. (4认同)
  • 我认为这对我来说是一个关键的答案.很多用例将有一个进程插入事物和另一个清理/删除它们,所以使用d.items()的建议工作.Python 3警告不能承受 (3认同)
  • @JonathonReinhart:不,我没有这方面的参考,但我认为它在Python中是相当标准的。例如,Alex Martelli 是一名 Python 核心开发人员,并且[在此处演示了其用法](/sf/answers/162087061/)。 (3认同)

mur*_*d99 22

你不能这样做,至少是这样d.iteritems().我试了一下,Python失败了

RuntimeError: dictionary changed size during iteration
Run Code Online (Sandbox Code Playgroud)

如果你改用d.items(),那就行了.

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

  • 除了Python 3 ... (2认同)
  • 我在回答中添加了Python 3. (2认同)
  • 仅供参考,Py2的`d.items()`到Py3的字面翻译(例如用于'2to3`)是`list(d.items())`,尽管`d.copy().items()`是可能效率相当. (2认同)
  • 如果 dict 对象非常大,那么 d.copy().items() 是否有效? (2认同)

com*_*ave 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
Run Code Online (Sandbox Code Playgroud)

第一个示例调用g(k),并抛出异常(字典在迭代期间改变了大小).

第二个示例调用h(k)并且不抛出任何异常,但输出:

{21: 'axx', 22: 'bxx', 23: 'cxx'}
Run Code Online (Sandbox Code Playgroud)

看看代码,看起来是错的 - 我本来期望的是:

{11: 'ax', 12: 'bx', 13: 'cx'}
Run Code Online (Sandbox Code Playgroud)


2cy*_*kyl 6

我有一个包含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
Run Code Online (Sandbox Code Playgroud)

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