python中遍历列表的同时去删除元素,其实是个小坑,对于初学者比较容易犯错;我记得是2013年时,遇到过类似的问题,当时也不清楚为什么出一些奇怪的问题/报错,反正用深拷贝就绕过去了。最近看到一篇文章提到了,我也找资料回顾记录一下。
问题描述
错误代码1:
1 2 3 4 5 6 |
list1 = [1, 2, 3, 4, 5, 2, 2] for i in list1: if i == 2: list1.remove(i) print(list1) |
运行输出为:[1, 3, 4, 5, 2]
不符合预期,最后一个2没有被删掉。
如下的错误代码2,这么写,会遇到语法报错。
1 2 3 4 5 6 |
list1 = [1, 2, 3, 4, 5, 2, 2] for i in range(len(list1)): if list1[i] == 2: list1.remove(list1[i]) print(list1) |
得到如下报错信息:
1 2 3 4 5 |
Traceback (most recent call last): File "/Users/jay/workspace/python.git/py2022/remove_item_in_for_list.py", line 3, in <module> if list1[i] == 2: IndexError: list index out of range |
问题分析
Python中用for发起任何形式的遍历时,它的遍历顺序都是从最初就确定的,而在遍历中删除了元素会导致当前索引的变化。错误1中,遍历中间取到的索引的那个值已经变了,会漏掉一些元素。错误2中,是删除元素后列表长度缩小了,所以会超出索引范围。
推荐几种正确的写法
1. 使用filter() 函数过滤符合条件的元素
1 2 3 4 |
list1 = [1, 2, 3, 4, 5, 2, 2] list2 = list(filter(lambda x: x!=2, list1)) print(list2) |
2. 列表推动(列表解析式)
1 2 3 4 |
list1 = [1, 2, 3, 4, 5, 2, 2] list2 = [i for i in list1 if i!=2] print(list2) |
3. 拷贝列表,遍历新列表时删除原列表中的内容 (或 相反)
通过列表切片生成新列表:
1 2 3 4 5 6 |
list1 = [1, 2, 3, 4, 5, 2, 2] for i in list1[:]: if i == 2: list1.remove(i) print(list1) |
通过深拷贝deepcopy生成新列表:
1 2 3 4 5 6 7 8 9 10 |
import copy list1 = [1, 2, 3, 4, 5, 2, 2] list2 = copy.deepcopy(list1) # 这种简单类型下浅拷贝也会正常;但list1的元素一旦有可变元素,就一定是要深拷贝才正确。 for i in list1: if i == 2: list2.remove(i) print(list2) |
4. 用while循环来做条件判断
1 2 3 4 |
while 2 in list1: list1.remove(2) print(list1) |
我个人喜欢用第2种列表推导 和 第3种复制列表 的方法。
参考:
https://segmentfault.com/a/1190000007214571
https://www.jianshu.com/p/230450957649