【问题标题】:Strange result when removing item from a list while iterating over it迭代列表时从列表中删除项目时出现奇怪的结果
【发布时间】:2022-01-24 20:22:47
【问题描述】:

我有这段代码:

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]
    

    【讨论】:

      【解决方案2】:

      @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))
      

      【讨论】:

        【解决方案3】:

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

        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 这个答案!优雅、简单……没有完全混淆。
        【解决方案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)
              

              【讨论】:

                猜你喜欢
                • 2011-09-09
                • 2014-10-26
                • 2020-05-27
                相关资源
                最近更新 更多