如何在使用 range() 函数从列表中删除项目时迭代列表? [复制]
Posted
技术标签:
【中文标题】如何在使用 range() 函数从列表中删除项目时迭代列表? [复制]【英文标题】:How to iterate a list while deleting items from list using range() function? [duplicate] 【发布时间】:2012-05-19 08:22:31 【问题描述】:这是我在尝试学习 python 编程时遇到的最常见的问题。问题是,当我尝试使用“range()”函数迭代列表以检查列表中的给定项目是否满足给定条件,如果是则删除它,它总是会给出“IndexError”。那么,有没有一种特殊的方法可以在不使用任何其他中间列表或“while”语句的情况下做到这一点?下面是一个例子:
l = range(20)
for i in range(0,len(l)):
if l[i] == something:
l.pop(i)
【问题讨论】:
查看过滤器内置功能。这就是你想要的。 你为什么要在条件是 i==something 的情况下编写这样的代码?在这种情况下,我不是一个非常有用的比较项目 @TJD 你是莱特,我的意思是“l[i]”。 【参考方案1】:首先,您永远不想在 Python 中迭代类似的东西。迭代实际对象,而不是索引:
l = range(20)
for i in l:
...
您出错的原因是您正在删除一个项目,因此后来的索引不复存在。
现在,您不能在循环访问列表时修改它,但这不是问题。更好的解决方案是在此处使用list comprehension,以过滤掉多余的项目。
l = range(20)
new_l = [i for i in l if not i == something]
您也可以使用the filter()
builtin,尽管在大多数情况下这往往不清楚(在您需要lambda
的地方会更慢)。
另请注意,在 Python 3.x 中,range()
生成的是生成器,而不是列表。
使用更具描述性的变量名称也是一个好主意 - 我假设这里是示例,但是像 i
和 l
这样的名称很难阅读并且更容易引入错误。
编辑:
如果您希望更新现有列表,如 cmets 中所指出的,您可以使用切片语法依次替换列表中的每个项目 (l[:] = new_l
)。也就是说,我认为这种情况是非常糟糕的设计。您不希望一段代码依赖以这种方式从另一段代码更新的数据。
编辑 2:
如果出于任何原因,您在遍历项目时需要索引,这就是 the enumerate()
builtin 的用途。
【讨论】:
假设您只有一个名称引用该列表。 @DavidHeffernan 如果需要,您可以将新列表重新分配给旧列表。 如果您在代码中有其他无法立即控制的引用,则不会。我知道有点牵强,但有时需要就地修改。 @DavidHeffernan,所以在这种情况下使用切片分配来就地更新列表。 @DavidHeffernan 然后可以使用切片语法依次替换列表中的每一项(l[:] = new_l
)。也就是说,我认为这种情况是非常糟糕的设计。您不希望一段代码依赖以这种方式从另一段代码更新的数据。【参考方案2】:
你总是可以用列表理解来做这种事情:
newlist=[i for i in oldlist if not condition ]
【讨论】:
列表理解...很好。来自我的 +1【参考方案3】:正如其他人所说,遍历列表并创建一个仅包含您想要保留的项目的新列表。
使用切片分配就地更新原始列表。
l[:] = [item for item in l if item != something]
【讨论】:
【参考方案4】:你应该从另一面来看问题:当一个元素等于“某物”时,将它添加到一个列表中。使用列表理解:
l = [i for i in xrange(20) if i != something]
【讨论】:
【参考方案5】: 你不应该使用for i in range(0,len(l)):
,如果你需要索引,使用for i, item in enumerate(l):
,如果不需要,使用for item in l:
您不应该操纵您正在迭代的结构。遇到这种情况时,请改为遍历副本
不要命名变量 l(可能被误认为 1 或 I)
如果要过滤列表,请明确执行。使用 filter()
或列表推导
顺便说一句,在你的情况下,你也可以这样做:
while something in list_: list_.remove(something)
不过,这不是很有效。但根据上下文,它可能更具可读性。
【讨论】:
【参考方案6】:您获得IndexError
的原因是您在for 循环中迭代时更改了列表的长度。基本上,这就是逻辑......
#-- Build the original list: [0, 1, 2, ..., 19]
l = range(20)
#-- Here, the range function builds ANOTHER list, in this case also [0, 1, 2, ..., 19]
#-- the variable "i" will be bound to each element of this list, so i = 0 (loop), then i = 1 (loop), i = 2, etc.
for i in range(0,len(l)):
if i == something:
#-- So, when i is equivalent to something, you "pop" the list, l.
#-- the length of l is now *19* elements, NOT 20 (you just removed one)
l.pop(i)
#-- So...when the list has been shortened to 19 elements...
#-- we're still iterating, i = 17 (loop), i = 18 (loop), i = 19 *CRASH*
#-- There is no 19th element of l, as l (after you popped out an element) only
#-- has indices 0, ..., 18, now.
还请注意,您正在根据列表的索引做出“弹出”决定,而不是列表的索引单元格中的内容。这很不寻常——这是你的意图吗?或者你有没有 意思更像...
if l[i] == something:
l.pop(i)
现在,在您的具体示例中,(l[i] == i)
但这不是典型的模式。
尝试过滤器功能,而不是遍历列表。它是内置的(与许多其他列表处理功能一样:例如 map、sort、reverse、zip 等)
试试这个...
#-- Create a function for testing the elements of the list.
def f(x):
if (x == SOMETHING):
return False
else:
return True
#-- Create the original list.
l = range(20)
#-- Apply the function f to each element of l.
#-- Where f(l[i]) is True, the element l[i] is kept and will be in the new list, m.
#-- Where f(l[i]) is False, the element l[i] is passed over and will NOT appear in m.
m = filter(f, l)
列表处理函数与“lambda”函数密切相关——在 Python 中,后者是简短的匿名函数。所以,我们可以把上面的代码改写成……
#-- Create the original list.
l = range(20)
#-- Apply the function f to each element of l.
#-- Where lambda is True, the element l[i] is kept and will be in the new list, m.
#-- Where lambda is False, the element l[i] is passed over and will NOT appear in m.
m = filter(lambda x: (x != SOMETHING), l)
试一试,看看它是如何工作的!
【讨论】:
使用filter()
和lambda
比这里的列表理解更慢且不够清晰。
@Lattyware 同意。我对上面的列表理解解决方案+1。在这种情况下,我唯一需要注意的是,对于很多初学者来说,列表理解实际上不如更冗长的方法那么清晰。否则,我全力以赴。
与所有语言结构一样,在使用之前需要对其进行解释,是的,但我认为这并不意味着应该避免使用它们。
是的,我的意思是 l[i] 而不是“i”,编辑它。以上是关于如何在使用 range() 函数从列表中删除项目时迭代列表? [复制]的主要内容,如果未能解决你的问题,请参考以下文章