标题: Python遍历列表时删除元素 作者: wzhvictor 创建: 2016-10-19 更新: 2022-09-04 11:06 链接: https://scz.617.cn/python/201610190000.txt tk在科学养猪群里问bluerust、scz是否碰上过这个Python坑,示例1 -------------------------------------------------------------------------- bas = [ 'ba1', 'ba2', 'ba3', 'ba4', 'ba5' ] for ba in bas : print( ba ) if ( ba.find( 'ba' ) != -1 ) : bas.remove( ba ) print( bas ) print( bas ) -------------------------------------------------------------------------- 即遍历list的过程中动态删除元素。上述代码输出如下 ba1 ['ba2', 'ba3', 'ba4', 'ba5'] ba3 // ba2被跳过去了 ['ba2', 'ba4', 'ba5'] ba5 // ba4被跳过去了 ['ba2', 'ba4'] ['ba2', 'ba4'] // 列表未删干净 然后tk搜了个链接 https://segmentfault.com/a/1190000007214571 这篇讲得清楚,建议直接看原文,作者是wzhvictor。 对示例1做点改动,示例2 -------------------------------------------------------------------------- bas = [ 'ba1', 'ba2', 'ba3', 'ba4', 'ba5' ] for i in range( len( bas ) ) : print( i ) print( bas[i] ) if ( bas[i].find( 'ba' ) != -1 ) : del bas[i] print( bas ) print( bas ) -------------------------------------------------------------------------- 上述代码输出如下 0 ba1 ['ba2', 'ba3', 'ba4', 'ba5'] 1 ba3 ['ba2', 'ba4', 'ba5'] 2 ba5 ['ba2', 'ba4'] 3 Traceback (most recent call last): File "", line 3, in IndexError: list index out of range ['ba2', 'ba4'] 循环变量i只递增到3,进而抛出IndexError。 该坑的起因是,for循环中i的取值从最开始就固定了,实际上要求list在for循环中 保持不变;遍历list的过程中动态删除元素,导致list发生变化,而i仍固执地按原 计划递增遍历list,于是漏删元素、索引越界。 对此,wzhvictor给了5种解决方案。 方式1,利用filter函数 -------------------------------------------------------------------------- bas = [ 'ba1', 'ba2', 'ba3', 'ba4', 'ba5', 'tk' ] bas = list( filter( lambda ba:ba.find( 'ba' ) == -1, bas ) ) print( bas ) -------------------------------------------------------------------------- 方法2,重新构造list -------------------------------------------------------------------------- bas = [ 'ba1', 'ba2', 'ba3', 'ba4', 'ba5', 'tk' ] bas = [ba for ba in bas if ba.find( 'ba' ) == -1] print( bas ) -------------------------------------------------------------------------- 方法3,遍历list的拷贝,对原始list进行删除操作 -------------------------------------------------------------------------- bas = [ 'ba1', 'ba2', 'ba3', 'ba4', 'ba5', 'tk' ] for ba in bas[:] : if ( ba.find( 'ba' ) != -1 ) : bas.remove( ba ) print( bas ) -------------------------------------------------------------------------- 方法4 -------------------------------------------------------------------------- bas = [ 'ba', 'ba', 'ba', 'ba', 'ba', 'tk' ] while 'ba' in bas : bas.remove( 'ba' ) print( bas ) -------------------------------------------------------------------------- 方法5,倒序遍历 -------------------------------------------------------------------------- bas = [ 'ba1', 'ba2', 'ba3', 'ba4', 'ba5', 'tk' ] for i in range( len( bas )-1, -1, -1 ) : if ( bas[i].find( 'ba' ) != -1 ) : del bas[i] print( bas ) -------------------------------------------------------------------------- 就tk的示例1而言,方法4其实不适用,方法4适合从list中删除所有特定值。 这个坑我没踩过,用过方法2、3、4或者它们的变种。没像wzhvictor那样细究过for 循环中i取值从最开始就固定,但我本能地对循环中动态处理的对象不放心,又懒得 看Python文档,所以要么重新构造list,要么复制list再操作,完美避坑。今日看了 wzhvictor的文章,方法1没用过,方法5没想过,方法5比较骚包,我第一次见。 Python表面上的不确定性真多,也没啥大不了,我的经验是,能用简明直观确定性的 写法,就不要骚包玩花活,性能优化是后话。再就是,单元测试,无需再多强调。 后面是补充讨论 2022-09-04 04:27 bluerust -------------------------------------------------------------------------- /* * https://github.com/python/cpython/blob/ac4ddab405a36bd0e2b86bcd147b3a647b734492/Objects/listobject.c#L3216-L3238 */ static PyObject * listiter_next ( _PyListIterObject *it ) { PyListObject *seq; PyObject *item; assert( it != NULL ); seq = it->it_seq; if ( seq == NULL ) { return NULL; } assert( PyList_Check( seq ) ); if ( it->it_index < PyList_GET_SIZE( seq ) ) { item = PyList_GET_ITEM( seq, it->it_index ); ++it->it_index; Py_INCREF( item ); return item; } it->it_seq = NULL; Py_DECREF( seq ); return NULL; } -------------------------------------------------------------------------- 看list对象的listiter_next实现,其内部实现基于it->it_index来遍历,若在迭代 循环中修改了list本身,it->it_index并未得到相应更新。 2022-09-04 09:43 huyuguang 这个坑在C++ STL里也有类似的,只不过有个惯用法erase(it++),先erase,然后+1。 更简单的方法是直接remove_if后接erase。