字典在迭代时大小变化"与"字典键在迭代时变化"异常有何区别
我正在准备一些关于Python中迭代器如何工作的材料,现在我在写一部分内容,讲的是如果你在遍历一个字典时改变它,会发生什么。我知道这样做是不好的习惯,所以我也把这个写进了我的材料里,但我还是想试试看会发生什么。
下面的代码打印出了键5,并给我抛出了一个“在迭代过程中字典大小发生变化”的异常。所以在我进行更改并想继续到下一个键的时候,就出现了这个异常。
d = {5:5, 10:10, 15:15, 20:20}
for elem in d:
print(elem)
d[elem+1] = elem+1
这没问题。但后来我想到,如果我同时进行添加和删除操作,这样字典的大小在技术上并没有变化,会怎么样呢?于是我写了这个:
d = {5:5, 10:10, 15:15, 20:20, 25:25, 30:30, 35:35}
for elem in d:
print(elem)
del d[elem]
d[elem+1] = elem+1
它打印出了字典中所有的键(7个),其中一些是新添加的值,但在打印了7个值后,它抛出了一个“在迭代过程中字典的键发生变化”的异常。现在我真的很好奇,内部发生了什么,导致了这个结果。我还是知道这样做是不好的习惯,我只是想理解一下。谢谢!
2 个回答
在遍历Python列表的内容时,不要尝试添加或删除列表中的项,因为这样会让遍历的过程变得混乱。你应该先复制一份列表,然后在这份复制的列表上进行遍历,这样原来的列表就可以被修改。不过,如果你在遍历时添加或删除项,还是可能会遇到问题。在上面的例子中,你可以通过获取一个list
的键来解决这个问题,如下所示:
for elem in list(d):
想了解更多细节,可以查看映射类型 - dict。你也会发现,Python的官方文档在解释迭代器的工作原理方面做得非常好。
嗯,这确实是个“Pythonic”的问题:Python的字典和迭代器并不是为了处理动态变化而设计的,所以你看到的那些异常就是这个原因。
原因在于,当你遍历一个字典时,它会创建一个迭代器对象,这个对象会逐个跟踪字典的键。但是,如果你在遍历字典的同时修改它,Python就无法保证迭代的状态是完整的。这是因为修改字典可能会改变它的内部结构,从而可能使当前的迭代状态失效。
在你的第二段代码中,你在每次迭代时删除当前的键elem,并添加一个新的键elem+1,但当你删除一个键时,它会改变字典的内部结构。即使你立刻添加了一个新键,Python的迭代器也无法很好地处理这种情况,因为它并不是为了跟踪这样的动态变化而设计的。
当Python在迭代过程中尝试获取下一个键时,它发现字典的内部结构已经改变,这就导致了“字典在迭代过程中键发生变化”的异常。
为什么第一段代码会立即引发RunTimeError:
在第一段代码中,字典在迭代过程中改变了大小。Python无法在遍历字典时动态处理字典大小的变化。当你在迭代过程中添加一个新元素时,Python会检测到字典的大小已经从最初的状态发生了变化,这就违反了迭代器对字典中元素总数的假设。
为什么在第二段代码中没有发生这种情况:
在第二段代码中,你在遍历字典的同时删除和添加元素。Python并不会立即检测到字典大小的变化。它会继续遍历字典并进行修改,直到遍历结束。只有在for循环结束时,Python才会检查字典的大小是否在迭代过程中被修改过。
所以,在第二段代码中,异常是在完整遍历字典后才被引发,因为Python只在迭代结束时检查字典的大小。