迭代时修改list和dictionary,为什么在dict上会失败?

2022-01-24 00:00:00 python list dictionary loops iteration

问题描述

让我们考虑一下这段代码,它在每次迭代中删除一个项目时迭代 list:

Let's consider this code which iterates over a list while removing an item each iteration:

x = list(range(5))

for i in x:
    print(i)
    x.pop()

它将打印 0, 1, 2.由于列表中的最后两个元素在前两次迭代中被删除,因此只打印前三个元素.

It will print 0, 1, 2. Only the first three elements are printed since the last two elements in the list were removed by the first two iterations.

但是如果你在 dict 上尝试类似的东西:

But if you try something similar on a dict:

y = {i: i for i in range(5)}

for i in y:
    print(i)
    y.pop(i)

它将打印 0,然后引发 RuntimeError: dictionary changed size during iteration,因为我们在迭代时从字典中删除了一个键.

It will print 0, then raise RuntimeError: dictionary changed size during iteration, because we are removing a key from the dictionary while iterating over it.

当然,在迭代期间修改列表是不好的.但是为什么没有像字典那样引发 RuntimeError 呢?这种行为有什么好的理由吗?

Of course, modifying a list during iteration is bad. But why is a RuntimeError not raised as in the case of dictionary? Is there any good reason for this behaviour?


解决方案

我觉得原因很简单.lists 是有序的,dicts(在 Python 3.6/3.7 之前)和 sets 不是.因此,在您迭代时修改 lists 可能不是最佳实践,但它会导致一致、可重现和有保证的行为.

I think the reason is simple. lists are ordered, dicts (prior to Python 3.6/3.7) and sets are not. So modifying a lists as you iterate may be not advised as best practise, but it leads to consistent, reproducible, and guaranteed behaviour.

您可以使用它,例如,假设您想将具有偶数个元素的 list 分成两半并反转第二半:

You could use this, for example let's say you wanted to split a list with an even number of elements in half and reverse the 2nd half:

>>> lst = [0,1,2,3]
>>> lst2 = [lst.pop() for _ in lst]
>>> lst, lst2
([0, 1], [3, 2])

当然,有很多更好、更直观的方法来执行此操作,但关键是它有效.

Of course, there are much better and more intuitive ways to perform this operation, but the point is it works.

相比之下,dicts 和 sets 的行为完全是特定于实现的,因为迭代顺序可能会根据散列而改变.

By contrast, the behaviour for dicts and sets is totally implementation specific since the iteration order may change depending on the hashing.

你会得到一个带有 collections.OrderedDictRunTimeError,大概是为了与 dict 行为保持一致.我认为在 Python 3.6 之后可能不会对 dict 行为进行任何更改(其中 dict 保证保持插入顺序),因为它会破坏向后兼容性而没有真正的用例.

You get a RunTimeError with collections.OrderedDict, presumably for consistency with the dict behaviour. I don't think any change in the dict behaviour is likely after Python 3.6 (where dicts are guaranteed to maintain insertion ordered) since it would break backward compatibility for no real use cases.

注意 collections.deque 在这种情况下也会引发 RuntimeError,尽管是有序的.

Note that collections.deque also raises a RuntimeError in this case, despite being ordered.

相关文章