duc*_*inh 29 python iteration dictionary loops list
让我们考虑这个迭代列表的代码,同时每次迭代删除一个项目:
x = list(range(5))
for i in x:
print(i)
x.pop()
Run Code Online (Sandbox Code Playgroud)
它会打印出来0, 1, 2.由于前两次迭代删除了列表中的最后两个元素,因此仅打印前三个元素.
但是如果你在dict上尝试类似的东西:
y = {i: i for i in range(5)}
for i in y:
print(i)
y.pop(i)
Run Code Online (Sandbox Code Playgroud)
它将打印0,然后提升RuntimeError: dictionary changed size during iteration,因为我们正在迭代它时从字典中删除一个键.
当然,在迭代期间修改列表是不好的.但是为什么RuntimeError不像字典那样提出?这种行为有什么好的理由吗?
Chr*_*nds 30
我认为原因很简单.lists是有序的,dicts(在Python 3.6/3.7之前)而sets则不是.因此list,在迭代时修改s可能不会被建议为最佳实践,但它会导致一致,可重现且有保证的行为.
你可以使用它,例如假设你想要将list偶数个元素分成两半并反转下半部分:
>>> lst = [0,1,2,3]
>>> lst2 = [lst.pop() for _ in lst]
>>> lst, lst2
([0, 1], [3, 2])
Run Code Online (Sandbox Code Playgroud)
当然,有更好,更直观的方法来执行此操作,但重点是它的工作原理.
相比之下,dicts和sets 的行为完全是特定于实现的,因为迭代顺序可能根据散列而改变.
你得到一个RunTimeError有collections.OrderedDict,大概是与一致性dict行为.我不认为dict在Python 3.6之后行为有任何改变(其中dicts保证保持插入顺序),因为它会破坏没有实际用例的向后兼容性.
请注意,尽管被命令,但在这种情况下collections.deque也提出了一个RuntimeError.
在不破坏向后兼容性的情况下,不可能在列表中添加这样的检查。对于字典,没有这样的问题。
在较早的迭代器设计中,for循环通过调用具有增加的整数索引的序列元素检索钩子来工作,直到引发IndexError为止。(我会说__getitem__,但是这是在类型/类统一之前,所以C类型没有__getitem__。)len甚至没有参与此设计,并且没有地方检查修改。
当引入迭代器时,dict迭代器从将迭代器引入语言的第一笔提交中就进行了大小更改检查。在此之前,所有Dict都是不可迭代的,因此没有向后兼容的问题。不过,列表仍然通过了旧的迭代协议。
当list.__iter__被引入,它是一个纯粹的速度优化,并非是一个行为改变,并添加修改检查将打破与上旧的行为依靠现有代码的向后兼容性。