Python:迭代弹出元素时列表索引超出范围错误

45

我写了一个简单的Python程序

l=[1,2,3,0,0,1]
for i in range(0,len(l)):
       if l[i]==0:
           l.pop(i)

我在代码的第if l[i]==0:行遇到了“索引超出范围”的错误。

通过调试,我发现i在增加并且列表在减少。然而,我的循环终止条件是i < len(l)。那么为什么会出现这样的错误?


1
我有一个循环终止条件 i < len(l)。你为什么这么说?在你的代码中哪里看到了这个? - S.Lott
1
@ S. Lott,i在range(0,len())中的意思是“i将循环到len(l)-1”。 - atv
4
另一个 Python 小技巧 - 你可以只写 range(len(l)),因为默认起始值是0。 - abyx
11
从 PEP 8 中可以看到:永远不要使用字符 l(小写字母 el)、O(大写字母 oh)或 I(大写字母 eye)作为单个字符变量名。 http://www.python.org/dev/peps/pep-0008/ - Stephan202
@atv:你为什么认为range(0,len(l))的结果会随着l的改变而变化?你为什么这样认为?你在哪里读到的? - S.Lott
可能是在迭代时从列表中删除项目的重复问题。 - tripleee
12个回答

67
你正在迭代列表l时减少其长度,因此当你接近范围语句中的索引结尾时,一些索引不再有效。
看起来你想要做的是:
l = [x for x in l if x != 0]

这段代码会返回一个不含有任何零元素的列表副本(这个操作被称为列表推导)。你甚至可以将最后一部分缩短为if x,因为非零数会被视为True
在您编写的代码中,不存在i < len(l)这样的循环终止条件,因为len(l)是在循环之前预先计算的,而不是在每次迭代时重新计算。不过,您确实可以以这样的方式编写它:
i = 0
while i < len(l):
   if l[i] == 0:
       l.pop(i)
   else:
       i += 1

19

len(l)表达式只会在range()内置函数被调用时计算一次。那时构建的范围对象不会改变, 它无法了解关于对象l的任何信息。

附注:l是一个糟糕的值名称!它看起来像数字1或大写字母I。


6

Mark Rushakoff所说的是正确的,但是如果你在for循环中按相反的顺序迭代,也可以从列表中删除元素。例如:

x = [1,2,3,0,0,1]
for i in range(len(x)-1, -1, -1):
    if x[i] == 0:
        x.pop(i)

这就像一座高楼从顶部倒塌到底部:即使它正在崩溃的过程中,你仍然可以“进入”并访问尚未倒塌的楼层。


6
您正在迭代列表时更改其大小,这可能不是您想要的,也是错误的原因。
编辑:如其他人所回答和评论的那样,列表推导式是更好的第一选择,尤其是对于这个问题。我提供了这个作为替代选择,虽然不是最好的答案,但仍然解决了问题。
因此,在这种情况下,您还可以使用“filter”,它允许您调用一个函数来评估您不想要的列表中的项目。
示例:
>>> l = [1,2,3,0,0,1]
>>> filter(lambda x: x > 0, l)
[1, 2, 3]

活到老,学到老。 简单就是美,除非你需要复杂的东西。


4
不需要lambda函数,因为0会被视为False。 filter(None, l) - Steve Losh
@Steve Losh - 这就是我喜欢 Stack Overflow 的原因... 学习这样的简单小技巧可以在长期节省我的击键!谢谢! - jathanism
1
为什么要向初学者介绍这种过时的方法呢?现在,列表推导式是首选的方式来完成这个任务。 - nikow
1
如果可以使用列表推导式完成任务,那么过滤或映射时应该始终使用列表推导式。 - Nick Bastin
你们说得没错,但是filter仍然是工具箱中重要的一部分,当你需要更高级的评估时就会用到它。 - jathanism

4
我认为解决这个问题的最佳方式是:
l = [1, 2, 3, 0, 0, 1]
while 0 in l:
    l.remove(0)

不要迭代列表,而是删除0,直到列表中没有任何0


2

列表推导式将帮助您找到解决方案。

但在Python中复制对象的正确方法是使用Python模块copy——浅层复制和深层复制操作。

l=[1,2,3,0,0,1]
for i in range(0,len(l)):
   if l[i]==0:
       l.pop(i)

如果不是这样的话,
import copy
l=[1,2,3,0,0,1]
duplicate_l = copy.copy(l)
for i in range(0,len(l)):
   if l[i]==0:
       m.remove(i)
l = m

那么,你自己的代码本来可以工作。 但为了优化,列表推导式是一个不错的解决方案。


1
问题在于您尝试在使用列表len()的循环内修改引用的列表。当您从列表中删除项目时,下一个循环将计算新的len()

例如,在第一次运行后,当您使用l.pop(i)移除(i)时,这是成功的,但在下一个循环中,列表的长度已经改变,因此所有索引号都已经被移动。到某个点时,循环尝试在缩短的列表上运行并抛出错误。

在循环外部执行此操作可以工作,但最好在循环之前首先声明一个空列表,并在循环内部将要保留的所有内容追加到新列表中。

对于那些可能遇到同样问题的人们。


1
我正在使用Python 3.3.5。使用while循环的上述解决方案对我没有用。即使在len(l)之后放置print(i),仍会出现错误。我在命令行(shell)中运行相同的代码,它可以正常运行而不出错。我的做法是,在主程序中计算len(l)并将其作为参数传递。这样就可以正常工作了。有时候,Python确实很奇怪。

0

我觉得这里大多数的解决方案都是关于列表推导式,但如果你想进行原地删除并且将空间复杂度保持在O(1)的话;解决方案是:

i = 0
for j in range(len(arr)):
if (arr[j] != 0):
    arr[i] = arr[j]
    i +=1
arr = arr[:i] 

0

代码:

while True:
        n += 1
        try:
          DATA[n]['message']['text']
        except:
          key = DATA[n-1]['message']['text']
          break

控制台:

Traceback (most recent call last):
  File "botnet.py", line 82, in <module>
    key =DATA[n-1]['message']['text']
IndexError: list index out of range

来自评论区:嗨,请不要只回答源代码。请尝试提供有关您解决方案如何工作的良好描述。请参阅:如何撰写好答案?。谢谢。 - sɐunıɔןɐqɐp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接