遍历的list 的时候 直接删除元素 注意事项
概要
记得几年前 那个时候刚刚学习Python 这种语言,就了解这门比较简单易学,容易上手,这一点毋庸置疑, 但是随着对于Python 语言的理解,觉得这本语言并不简单,也有很多时候 不注意容易写出来的bug ,今天我就来分享一下 ,那些年 我曾经写过的bug ,希望可以帮助 新同学 及早地避免这样的bug .
问题引入举例
举一个很常见的例子,在列表中 过滤元素, 有一个列表我不希望保留某个元素的值,比如删除 元素为3
的值
# 有一个list, list 中存放int类型的数据 , 删除 元素为 3 的值,返回一个list
from typing import List
def delete_arr(arr: List[int]) -> List[int]:
for num in arr:
# print(f"num:{num}")
if num == 3:
arr.remove(num)
return arr
if __name__ == '__main__':
arr = [1, 2, 3, 4, 5, 6, 7]
arr2 = delete_arr(arr)
print(arr2) # [1, 2, 4, 5, 6, 7]
看到这样的结果 也没有问题, 成功的删除了对应为3
的元素
这段代码 是有潜在的问题的, 假如 我现在 arr 为 arr = [1, 2, 3, 3, 4, 5, 3, 6]
,在跑一次 上面的代码 结果如下:
[1, 2, 4, 5, 3, 6]
发现 3
这个元素 没有完全删除,这是为什么呢?
要想解决这个问题, 我们对Python 语言 for 循环这个语法更加有深刻的理解。
for item in iterable
for 循环的对象 是一个 可迭代对象, 每次其实调用的是 可迭代对象的 __next__
产生的下一个元素.
迭代器
我们来实现 一个简单的迭代器,在接口的层次我们 实现两个魔术方法, __iter__
和 __next__
这两个方法。
__iter__
方法用来返回一个可迭代对象, next 返回下一个迭代的元素,如果迭代到最后的一个元素 抛出一个异常StopIteration
迭代就结束了。
class MyIterator:
"""
实现一个 迭代器
"""
def __init__(self, max_size=5, count=0):
self.count = count
self.max_size = max_size
def __iter__(self):
return self
def __next__(self):
self.count += 1
if self.count > self.max_size:
raise StopIteration('stop')
return self.count
>>> collection = MyIterator()
>>>
>>> iterator = iter(collection)
>>> next(iterator)
1
>>> next(iterator)
2
>>> next(iterator)
3
>>> next(iterator)
4
>>> next(iterator)
5
>>> next(iterator)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 18, in __next__
StopIteration: stop
collection = MyIterator()
for item in collection:
print(item) # 1 2 3 4 5
有了这些基础知识后,我们开始继续讲解,遍历list 的时候删除元素发生了什么?
arr = [1, 2, 3, 3, 4, 5, 3, 6]
以这个为例子吧
def delete_arr(arr: List[int]) -> List[int]:
for num in arr:
# print(f"num:{num}")
if num == 3:
arr.remove(num)
return arr
当我们在列表中删除元素的时候 发生了什么? 第一 list 长度要减一,第二删除的元素 如果处于中间的元素, 那么之后的元素,就会依次前移 占中这个位置。 然后 for 循环 此时并不知道 list 的长度发生了改变。
下面 我画了一个图,解释一下。
1.当遍历到3 的时候,此时 有一个remove操作
2.当 list.remove(3)
后, 后面的元素 整体前移。
3.此时下一个元素 为4, 注意 此时 第二个3 这个元素 ,就跳过去了.
4.随着迭代的不断进行,终于来到了, 最后一个3 的为位置
5.最后迭代到了最后一个元素
来分析一下:
当删除元素的时候,删除元素之后 位置 会前移, 此时迭代器并不能知道 当前的 list 会发生了变化,它只是调用next 方法 进行 迭代元素, 这样就有可能漏掉元素,比如3 会被漏掉, 第二点 remove(e) 这个方法,只会移除,第一个等于e 的元素,并且是从头开始 看,发现移除就返回了。
s.remove(element)
remove the **first item** from *s* where `s[i]` is equal to *x*
问题解决方案
复制一份数组
进行迭代 的时候 使用 副本 进行迭代,然后 删除元素还是在原来的基础上进行。
arr[:]
这个 就是 切片用法,默认就是 arr[0:lastIndex]
, 这里相当于复制一份数据。
def delete_arr(arr: List[int]) -> List[int]:
for num in arr[:]:
if num == 3:
arr.remove(num)
return arr
总结
本文对for 循环了做了比较多的介绍,解释了for 循环如何运行的,知道如何实现一个迭代器,顺便解决了一个小的bug. 希望可以帮助到你,加油,同学。
之前写的一些比较旧的文章 仅供参考