【问题标题】:Strange result when removing item from a list while iterating over it遍历列表时从列表中删除项目时出现奇怪的结果
【发布时间】:2011-09-09 17:54:22
【问题描述】:

我有这段代码:

numbers = range(1, 50)

for i in numbers:
    if i < 20:
        numbers.remove(i)

print(numbers)

但我得到的结果是:
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]

当然,我希望 20 以下的数字不会出现在结果中。看起来我在删除时做错了什么。

【问题讨论】:

    标签: python list loops


    【解决方案1】:

    您在迭代列表时正在修改它。这意味着第一次通过循环,i == 1,所以 1 从列表中删除。然后for 循环转到列表中的第二项,不是2,而是3!然后将其从列表中删除,然后for 循环继续到列表中的第三项,现在是 5。依此类推。也许这样更容易可视化,用 ^ 指向 i 的值:

    [1, 2, 3, 4, 5, 6...]
     ^
    

    这是列表最初的状态;然后删除 1 并循环到列表中的第二项:

    [2, 3, 4, 5, 6...]
        ^
    [2, 4, 5, 6...]
           ^
    

    等等。

    在迭代列表时没有改变列表长度的好方法。你能做的最好的事情是这样的:

    numbers = [n for n in numbers if n >= 20]
    

    或者这个,用于就地更改(括号中的东西是一个生成器表达式,它在切片分配之前被隐式转换为一个元组):

    numbers[:] = (n for in in numbers if n >= 20)
    

    如果您想在删除之前对 n 执行操作,您可以尝试的一个技巧是:

    for i, n in enumerate(numbers):
        if n < 20 :
            print("do something")
            numbers[i] = None
    numbers = [n for n in numbers if n is not None]
    

    【讨论】:

    • 有关for 保留来自Python 文档docs.python.org/3.9/reference/… 的索引的相关说明:“当循环修改序列时有一个微妙之处(这只会发生在可变序列中,例如列表)。内部计数器用于跟踪接下来使用哪个项目,并且在每次迭代时递增。...这意味着如果套件从序列中删除当前(或前一个)项目,下一项将被跳过(因为它获取了已处理的当前项的索引)。"
    【解决方案2】:

    从列表中删除项目很简单:从列表的末尾开始:

    li = range(1,15)
    print li,'\n'
    
    for i in xrange(len(li)-1,-1,-1):
        if li[i] < 6:
            del li[i]
    
    print li
    

    结果

    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] 
    
    [6, 7, 8, 9, 10, 11, 12, 13, 14]
    

    【讨论】:

    • 我多么希望我能 +2 这个答案!优雅、简单……没有完全混淆。
    【解决方案3】:

    @senderle's 答案是正确的方法!

    话虽如此,为了进一步说明你的问题,如果你仔细想想,你总是想删除索引 0 二十次:

    [1,2,3,4,5............50]
     ^
    [2,3,4,5............50]
     ^
    [3,4,5............50]
     ^
    

    所以你实际上可以这样做:

    aList = list(range(50))
    i = 0
    while i < 20:
        aList.pop(0)
        i += 1
    
    print(aList) #[21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
    

    希望对你有帮助。


    以下是 不是 AFAIK 的不良做法。

    编辑(更多):

    lis = range(50)
    lis = lis[20:]
    

    也会做这项工作。

    EDIT2(我很无聊):

    functional = filter(lambda x: x> 20, range(50))
    

    【讨论】:

      【解决方案4】:

      所以我找到了一个解决方案,但它真的很笨拙......

      首先创建一个索引数组,在其中列出所有要删除的索引,如下所示

      numbers = range(1, 50)
      index_arr = []
      
      for i in range(len(numbers):
          if numbers[i] < 20:
              index_arr.append(i)
      
      

      之后,您想从数字列表中删除所有条目,并将索引保​​存在 index_arr 中。您将遇到的问题与以前相同。因此,在从数字 arr 中删除一个数字后,您必须从 index_arr 中的每个索引中减去 1,如下所示:

      numbers = range(1, 50)
      index_arr = []
      
      for i in range(len(numbers):
          if numbers[i] < 20:
              index_arr.append(i)
      
      for del_index in index_list:
          numbers.pop(del_index)
      
          #the nasty part
          for i in range(len(index_list)):
              index_list[i] -= 1
      

      它会起作用,但我想这不是预期的方式

      【讨论】:

        【解决方案5】:

        作为@Senderle 回答的附加信息,仅供记录,我认为当python 在“序列类型”上看到for 时,可视化场景背后的逻辑会很有帮助。

        假设我们有:

        lst = [1, 2, 3, 4, 5]
        
        for i in lst:
            print(i ** 2)
        

        实际上是这样的:

        index = 0
        while True:
            try:
                i = lst.__getitem__(index)
            except IndexError:
                break
            print(i ** 2)
            index += 1
        

        就是这样,当我们在序列类型或 Iterables 上使用 for 时,有一个 try-catch 机制(虽然有点不同 - 调用 next()StopIteration 异常)。

        *我想说的是,python 将在此处跟踪一个名为 index 的自变量,因此无论列表发生什么(删除或添加),python 都会递增该变量并调用 __getitem__()方法与“这个变量”并要求项目。

        【讨论】:

          【解决方案6】:

          您也可以使用 continue 来忽略小于 20 的值

          mylist = []
          
          for i in range(51):
              if i<20:
                  continue
              else:
                  mylist.append(i)
          print(mylist)
          

          【讨论】:

            【解决方案7】:

            以@eyquem 的答案为基础并简化答案...

            问题是当你迭代时元素被从你下面拉出来,当你前进到下一个数字时跳过数字

            如果您从头开始并向后移动,则在移动中删除项目并不重要,因为当它进入“下一个”项目(实际上是前一个项目)时,删除不会影响前半部分的列表。

            只需将reversed() 添加到您的迭代器即可解决问题。注释是一种很好的形式,可以防止未来的开发人员“整理”您的代码并神秘地破坏它。

            for i in reversed(numbers): # `reversed` so removing doesn't foobar iteration
              if i < 20:
                numbers.remove(i)
            

            【讨论】:

              【解决方案8】:

              由于Python 3.3,您可以使用列表copy() 方法作为迭代器:

              numbers = list(range(1, 50))
              
              for i in numbers.copy():
                  if i < 20:
                      numbers.remove(i)
              print(numbers)
              
              [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
              

              【讨论】:

                猜你喜欢
                • 2022-01-24
                • 2021-03-23
                • 2018-06-27
                相关资源
                最近更新 更多