迭代时修改list和dictionary,为什么在dict上会失败?
问题描述
让我们考虑一下这段代码,它在每次迭代中删除一个项目时迭代 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?
解决方案
我觉得原因很简单.list
s 是有序的,dict
s(在 Python 3.6/3.7 之前)和 set
s 不是.因此,在您迭代时修改 list
s 可能不是最佳实践,但它会导致一致、可重现和有保证的行为.
I think the reason is simple. list
s are ordered, dict
s (prior to Python 3.6/3.7) and set
s are not. So modifying a list
s 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.
相比之下,dict
s 和 set
s 的行为完全是特定于实现的,因为迭代顺序可能会根据散列而改变.
By contrast, the behaviour for dict
s and set
s is totally implementation specific since the iteration order may change depending on the hashing.
你会得到一个带有 collections.OrderedDict
的 RunTimeError
,大概是为了与 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 dict
s 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.
相关文章