如何在迭代时从列表中删除项目?

How to remove items from a list while iterating?

提问人:lfaraone 提问时间:7/30/2009 最后编辑:Ciro Santilli OurBigBook.comlfaraone 更新时间:1/25/2023 访问量:890989

问:

这个问题的答案是社区的努力。编辑现有答案以改进此帖子。它目前不接受新的答案或交互。

我正在遍历 Python 中的元组列表,如果它们满足某些条件,我会尝试删除它们。

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

我应该用什么来代替?我不知道如何以这种方式删除该项目。code_to_remove_tup

Python 迭代

评论

0赞 ggorlen 12/7/2019
此页面上的大多数答案并没有真正解释为什么在迭代列表时删除元素会产生奇怪的结果,但这个问题中公认的答案确实如此,对于第一次遇到此问题的初学者来说可能是一个更好的骗局。

答:

395赞 Lennart Regebro 7/30/2009 #1

您需要获取列表的副本并首先对其进行迭代,否则迭代将失败,并可能导致意外结果。

例如(取决于列表的类型):

for tup in somelist[:]:
    etc....

举个例子:

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]

评论

20赞 Lennart Regebro 6/18/2014
@Zen 因为第二个遍历了列表的副本。因此,在修改原始列表时,不会修改循环访问的副本。
3赞 Mariusz Jamro 2/4/2015
与list(somelist)相比,做somelist[:]有什么更好?
4赞 Lennart Regebro 2/5/2015
list(somelist)将可迭代对象转换为列表。 创建支持切片的对象的副本。所以他们不一定做同样的事情。在这种情况下,我想制作对象的副本,所以我使用somelist[:]somelist[:]
52赞 vitiral 2/12/2015
请注意,对于任何阅读本文的人来说,这对于列表来说非常慢。 每次迭代都必须检查整个列表,因此需要很长时间。remove()
19赞 Steve 8/7/2016
在处理只有十几个项目的列表时,大 O 时间并不重要。对于未来的程序员来说,通常清晰易懂,这比性能更有价值。
53赞 Eli Courtwright 7/30/2009 #2

对于这样的例子,最好的方法是列表推导

somelist = [tup for tup in somelist if determine(tup)]

如果你正在做一些比调用函数更复杂的事情,我更喜欢构造一个新列表,并在我进行时简单地附加到它。例如determine

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

复制列表可能会使代码看起来更简洁一些,如下面的答案之一所述。对于非常大的列表,您绝对不应该这样做,因为这涉及首先复制整个列表,然后对要删除的每个元素执行操作,使其成为一种算法。removeO(n)removeO(n^2)

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
1095赞 David Raznick 7/30/2009 #3

您可以使用列表推导式创建一个新列表,该列表仅包含您不想删除的元素:

somelist = [x for x in somelist if not determine(x)]

或者,通过分配给切片,您可以更改现有列表以仅包含所需的项目:somelist[:]

somelist[:] = [x for x in somelist if not determine(x)]

如果有其他参考资料需要反映这些变化,这种方法可能很有用。somelist

除了理解,您还可以使用 .在 Python 2 中:itertools

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

或者在 Python 3 中:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

评论

6赞 highBandWidth 4/21/2011
如果您知道只有少数几个会被删除,即只删除它们并将其他保留在原地而不是重写它们,您能加快速度吗?
57赞 jpcgt 11/16/2014
如果我的清单很大,买不起副本怎么办?
30赞 Rostislav Kondratenko 4/29/2015
@jpcgt您应该使用它来创建可能不会创建任何不必要的副本的生成器。somelist[:] = (x for x in somelist if determine(x))
9赞 jfs 5/8/2015
@RostislavKondratenko:在内部实现调用 PySequence_Fast() 的函数。这个函数总是返回一个列表,即@Alex已经使用列表而不是生成器的 Martelli 解决方案很可能更有效list_ass_slice()somelist[:]=
9赞 Bowen Liu 9/25/2018
您愿意解释一下将列表推导分配给列表和列表克隆之间有什么区别吗?原始列表不会在这两种方法中发生突变吗?somelist
184赞 John Machin 7/30/2009 #4
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

你需要倒退,否则这有点像锯掉你坐着的树枝:-)

Python 2 用户:替换为以避免创建硬编码列表rangexrange

评论

20赞 ncoghlan 3/23/2011
在最新版本的 Python 中,您可以使用内置的reversed()
20赞 ncoghlan 2/12/2015
reversed() 不会创建一个新列表,它会在提供的序列上创建一个反向迭代器。像 enumerate() 一样,您必须将其包装在 list() 中才能真正从中获取列表。您可能正在考虑 sorted(),它每次都会创建一个新列表(它必须这样做,因此它可以对其进行排序)。
2赞 Sam Watkins 9/15/2015
这是数组的 O(N*M),如果从大列表中删除许多项目,它会非常慢。所以不推荐。
2赞 Navin 2/8/2016
@SamWatkins是的,这个答案适用于从一个非常大的数组中删除几个元素的情况。内存使用量较少,但速度可能要慢几倍。m
2赞 Barmaley 5/5/2017
喜欢这个答案和砍树枝的比喻!只要您不必做任何复杂的事情,列表理解就会起作用。唯一的注释,我会在 python2 和 python3 中使用reversed(xrange(len(somelist)))reversed(range(len(somelist)))
41赞 André Eriksson 7/30/2009 #5

对于那些喜欢函数式编程的人:

somelist[:] = filter(lambda tup: not determine(tup), somelist)

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))

评论

1赞 ShadowRanger 10/22/2016
1. 列表推导和生成器表达式借鉴了 Haskell,一种纯函数式语言;它们的功能与 完全相同,而且更像 Python。2.如果你需要使用或,列表comp或genexpr总是更好的选择; 当 transform/predicate 函数是用 C 语言实现的 Python 内置函数并且可迭代对象不是很小时,它们可能会稍微快一些,但当您需要 listcomp/genexpr 可以避免的时,它们总是会变慢。filterlambdamapfiltermapfilterlambda
696赞 Alex Martelli 7/31/2009 #6

建议列表推导的答案几乎是正确的——除了他们构建了一个全新的列表,然后给它起了一个与旧列表相同的名称,他们没有就地修改旧列表。这与选择性删除不同,正如 Lennart 的建议一样——它更快,但如果通过多个引用访问您的列表,那么您只是重新替换其中一个引用而不更改列表对象本身这一事实可能会导致微妙的、灾难性的错误。

幸运的是,它非常容易获得列表推导的速度和就地更改所需的语义 - 只需编写代码:

somelist[:] = [tup for tup in somelist if determine(tup)]

请注意与其他答案的细微差别:这个答案没有分配给裸名。它分配给恰好是整个列表的列表切片,从而替换同一 Python 列表对象中的列表内容,而不是像其他答案一样重新替换一个引用(从以前的列表对象到新的列表对象)。

评论

1赞 PaulMcG 3/26/2011
如何使用字典进行相同的切片作业?在 Python 2.6 中?
12赞 Sven Marnach 4/2/2011
@Paul:由于字典是无序的,因此切片对于字典没有意义。如果要用 dict 的内容替换 dict 的内容,请使用 .aba.clear(); a.update(b)
1赞 Derek Dahmer 8/8/2011
为什么通过替换变量引用的内容来“重新拔插”其中一个引用会导致错误?这似乎只是在多线程应用程序中是一个潜在的问题,而不是单线程应用程序。
72赞 Steven T. Snyder 11/16/2011
@Derek 这重新分配给列表推导的结果,但仍然引用原始列表。如果您期望并引用同一列表,则可能引入了错误。您可以通过分配给整个列表的一部分来防止这种情况,正如 Alex 所示,我在这里显示:.列表已就地修改。确保对列表的所有引用(两者和此处)都引用新列表。x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];xy['foo','bar','baz']xyx = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];xy
0赞 John Strood 7/13/2016
事实上,使用函数也会创建一个新列表,不会修改元素......只filterolist[:] = [i for i in olist if not dislike(i)]
4赞 Xolve 1/9/2011 #7

您可能希望将 available 用作内置。filter()

有关更多详细信息,请查看此处

5赞 fantabolous 8/18/2014 #8

如果要在迭代期间执行任何其他操作,最好同时获取索引(这样可以保证您能够引用它,例如,如果您有一个字典列表)和实际列表项内容。

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

enumerate允许您同时访问项目和索引。 这样您以后要删除的索引就不会在您身上发生变化。reversed

评论

0赞 Mark Amery 6/22/2016
为什么在你有字典列表的情况下,获取索引比在任何其他类型的列表的情况下更相关?据我所知,这没有意义。
3赞 Queequeg 7/11/2015 #9

您可以尝试反向 for 循环,因此some_list您将执行以下操作:

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

这样,索引是对齐的,并且不会受到列表更新的影响(无论您是否弹出 cur 元素)。

评论

0赞 Mark Amery 6/22/2016
循环比自己计算索引更简单。reversed(list(enumerate(some_list)))
0赞 Queequeg 6/29/2016
@MarkAmery不认为你可以以这种方式改变列表。
74赞 Ciro Santilli OurBigBook.com 12/12/2015 #10

解决方法概述

也:

一般来说,如果你是快速而肮脏的,并且不想添加自定义类,你只想默认选择更快的选项,除非内存是一个大问题。LinkedList.append()

官方 Python 2 教程 4.2.“用于声明”

https://docs.python.org/2/tutorial/controlflow.html#for-statements

文档的这一部分清楚地表明:

  • 您需要复制迭代列表才能对其进行修改
  • 一种方法是使用切片表示法[:]

如果需要在循环中修改要循环访问的序列(例如,复制所选项目),建议您先进行复制。循环访问序列不会隐式创建副本。切片表示法使这特别方便:

>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words[:]:  # Loop over a slice copy of the entire list.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

Python 2 文档 7.3.“for 语句”

https://docs.python.org/2/reference/compound_stmts.html#for

文档的这一部分再次说明您必须制作副本,并给出了一个实际的删除示例:

注意:当序列被循环修改时,有一个微妙之处(这只能发生在可变序列中,即列表)。内部计数器用于跟踪接下来使用的项目,并在每次迭代时递增。当此计数器达到序列的长度时,循环终止。这意味着,如果套件从序列中删除当前(或上一个)项目,则将跳过下一个项目(因为它获取已处理的当前项目的索引)。同样,如果套件在当前项目之前插入序列中的项目,则下次通过循环将再次处理当前项目。这可能会导致令人讨厌的错误,可以通过使用整个序列的切片制作临时副本来避免,例如,

for x in a[:]:
    if x < 0: a.remove(x)

但是,我不同意这种实现,因为必须迭代整个列表才能找到值。.remove()

Python 能做得更好吗?

似乎这个特定的 Python API 可以改进。例如,将其与以下产品进行比较:

这两者都清楚地表明,除非使用迭代器本身,否则您不能修改正在迭代的列表,并且为您提供了在不复制列表的情况下进行修改的有效方法。

也许潜在的基本原理是假设 Python 列表是动态数组支持的,因此任何类型的删除无论如何都会在时间上效率低下,而 Java 具有更好的接口层次结构,具有 ArrayListLinkedList 实现。ListIterator

Python stdlib 中似乎也没有显式链表类型:Python 链表

评论

2赞 Lukali 7/9/2021
最后有人指出了实际的文档。在此之前,我无法理解任何答案。
3赞 rafa 12/16/2015 #11

我需要做类似的事情,就我而言,问题是内存 - 我需要将多个数据集对象合并到一个列表中,在对它们做了一些事情之后,作为一个新对象,并且需要删除我正在合并的每个条目,以避免复制所有这些条目并炸毁内存。就我而言,将对象放在字典而不是列表中工作正常:

```

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

```

12赞 ntk4 3/19/2016 #12

如果当前列表项满足所需的条件,则只创建一个新列表可能是明智的。

所以:

for item in originalList:
   if (item != badValue):
        newList.append(item)

为了避免使用新的列表名称重新编码整个项目:

originalList[:] = newList

注意,来自 Python 文档:

复制.copy(x) 返回 x 的浅拷贝。

复制.deepcopy(x) 返回 x 的深层副本。

评论

4赞 Mark Amery 6/22/2016
这不会增加几年前公认的答案中没有的新信息。
2赞 ntk4 6/23/2016
这很简单,只是@MarkAmery看待问题的另一种方式。对于那些不喜欢压缩编码语法的人来说,它不那么浓缩。
2赞 Resonance 5/17/2016 #13

顶级域名:

我写了一个库,允许你这样做:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

如果可能的话,最好使用另一种方法,在迭代时不需要修改可迭代对象,但对于某些算法来说,它可能没有那么简单。因此,如果您确定自己确实需要原始问题中描述的 Code Pattern,这是可能的。

应该适用于所有可变序列,而不仅仅是列表。


完整答案:

编辑:此答案中的最后一个代码示例给出了一个用例,说明为什么您有时可能想要就地修改列表而不是使用列表推导式。答案的第一部分是关于如何就地修改数组的教程。

该解决方案遵循 senderle 的这个答案(对于相关问题)。这解释了在循环访问已修改的列表时如何更新数组索引。下面的解决方案旨在正确跟踪数组索引,即使列表被修改也是如此。

这里下载,它只是一个文件,所以不需要安装git。没有安装程序,因此您需要确保该文件位于您自己的 python 路径中。该代码是为 python 3 编写的,未经在 python 2 上测试。fluidIter.pyhttps://github.com/alanbacon/FluidIterator

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

这将产生以下输出:

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

上面我们在流体列表对象上使用了该方法。还实现了其他常见的可迭代方法,例如 、 、 、 、 。也可以使用切片修改列表(并且未实现方法)。popdel fluidL[i].remove.insert.append.extendsortreverse

唯一的条件是,如果在任何时候或被重新分配给不同的列表对象,则代码将不起作用,则只能就地修改列表。原始对象仍将由 for 循环使用,但将超出我们修改的范围。fluidLlfluidL

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

如果我们想访问列表的当前索引值,我们不能使用 enumerate,因为这只计算 for 循环运行了多少次。相反,我们将直接使用迭代器对象。

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

这将输出以下内容:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

该类只是为原始列表对象提供包装器。原始对象可以作为流体对象的属性进行访问,如下所示:FluidIterable

originalList = fluidArr.fixedIterable

更多示例/测试可以在 底部的部分找到。这些值得一看,因为它们解释了在各种情况下会发生什么。例如:使用切片替换列表的大部分内容。或者在嵌套的 for 循环中使用(和修改)相同的迭代对象。if __name__ is "__main__":fluidIter.py

正如我一开始所说:这是一个复杂的解决方案,会损害代码的可读性,并使调试更加困难。因此,应首先考虑其他解决方案,例如David Raznick的答案中提到的列表推导式。话虽如此,我发现这个类有时对我有用,并且比跟踪需要删除的元素的索引更容易使用。


编辑:正如评论中提到的,这个答案并没有真正提出这种方法提供解决方案的问题。我将尝试在这里解决这个问题:

列表推导式提供了一种生成新列表的方法,但这些方法倾向于孤立地看待每个元素,而不是将列表的当前状态作为一个整体。

newList = [i for i in oldList if testFunc(i)]

但是,如果结果取决于已经添加的元素呢?或者接下来可能会添加仍在其中的元素?可能仍然有一种使用列表推导的方法,但它将开始失去它的优雅,对我来说,修改列表感觉更容易。testFuncnewListoldList

下面的代码是遭受上述问题的算法的一个示例。该算法将减少列表,以便任何元素都不是任何其他元素的倍数。

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

输出和最终缩减列表如下所示

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]

评论

0赞 Mark Amery 6/22/2016
很难说这是否是过度设计的,因为不清楚它试图解决什么问题;使用这种方法删除元素可以实现哪些无法实现的目标?如果没有答案,为什么有人会相信下载和使用带有拼写错误和注释掉代码的 600 行库比单行代码更能解决他们的问题?-1.some_list[:] = [x for x in some_list if not some_condition(x)]
0赞 Resonance 6/28/2016
@MarkAmery。当这种情况发生时,主要用例是尝试确定是否应删除(或添加或移动)项时,不仅基于项本身,还基于列表中另一个项的状态或整个列表的状态。例如,使用列表推导式无法编写类似 where is a different list element from 的内容。也不可能写.some_list[:] = [x for x in some_list if not some_condition(y)]yxsome_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)]
5赞 Alexey 9/2/2016 #14

一种可能的解决方案,如果您不仅想删除某些内容,还想在单个循环中对所有元素执行某些操作,则非常有用:

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1

评论

0赞 Beefster 3/16/2018
你真的应该只使用理解。它们更容易理解。
1赞 Alexey 3/16/2018
如果我想删除一些东西,用它做一些事情,并在一个循环中对事情做一些事情,该怎么办?badgood
1赞 Beefster 3/30/2018
实际上,我意识到这里有一些聪明之处,因为您可以使用打开的切片()复制列表,并且由于您可能正在做一些花哨的事情,因此它实际上有一个用例。好的修订是好的。接受我的赞成票。alist[:]
11赞 Cinghiale 10/21/2016 #15

这个答案最初是为了回答一个后来被标记为重复的问题而写的:从 python 上的列表中删除坐标

代码中存在两个问题:

1)使用remove()时,您尝试删除整数,而需要删除元组。

2) for 循环将跳过列表中的项目。

让我们来看看执行代码时会发生什么:

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

第一个问题是你同时将 'a' 和 'b' 传递给 remove(),但 remove() 只接受一个参数。那么我们如何才能让 remove() 与您的列表正常工作呢?我们需要弄清楚您列表中的每个元素是什么。在本例中,每个元组都是一个元组。为了看到这一点,让我们访问列表的一个元素(索引从 0 开始):

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

啊哈!L1 的每个元素实际上都是一个元组。所以这就是我们需要传递给 remove() 的内容。python 中的元组非常简单,它们只需将值括在括号中即可。“a, b” 不是元组,但 “(a, b)” 是元组。因此,我们修改您的代码并再次运行它:

# The remove line now includes an extra "()" to make a tuple out of "a,b"
L1.remove((a,b))

此代码运行没有任何错误,但让我们看一下它输出的列表:

L1 is now: [(1, 2), (5, 6), (1, -2)]

为什么 (1,-2) 仍然在您的列表中?事实证明,在不特别小心的情况下,在使用循环遍历列表的同时修改列表是一个非常糟糕的主意。(1, -2) 保留在列表中的原因是,在 for 循环的迭代之间,列表中每个项目的位置都发生了变化。让我们看看如果我们给上面的代码提供一个更长的列表会发生什么:

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
### Outputs:
L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

从该结果可以推断,每次条件语句的计算结果为 true 并删除列表项时,循环的下一次迭代将跳过对列表中下一项的计算,因为其值现在位于不同的索引处。

最直观的解决方案是复制列表,然后遍历原始列表,只修改副本。您可以尝试这样做:

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

但是,输出将与之前相同:

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

这是因为当我们创建 L2 时,python 实际上并没有创建一个新对象。相反,它只是将 L2 引用到与 L1 相同的对象。我们可以用“is”来验证这一点,它不同于“equals”(==)。

>>> L2=L1
>>> L1 is L2
True

我们可以使用 copy.copy() 制作一个真正的副本。然后一切按预期工作:

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

最后,还有一个比制作一个全新的 L1 副本更干净的解决方案。reversed() 函数:

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

不幸的是,我无法充分描述reversed()的工作原理。当列表传递给它时,它会返回一个“listreverseiterator”对象。出于实际目的,您可以将其视为创建其参数的反向副本。这是我推荐的解决方案。

24赞 Michael 3/14/2017 #16

我需要用一个巨大的列表来做到这一点,复制列表似乎很昂贵,特别是因为在我的情况下,与剩余的项目相比,删除的数量很少。我采用了这种低级方法。

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

我不知道的是,与复制一个大列表相比,几次删除的效率如何。如果您有任何见解,请发表评论。

评论

0赞 gustavovelascoh 5/5/2017
就我而言,我需要将这些“不需要”的元素移动到另一个列表中。你对这个解决方案有什么新的评论吗?我还认为最好使用一些删除而不是重复列表。
0赞 max 5/5/2017
如果性能是一个问题,这是正确的答案(尽管与@Alexey相同)。也就是说,首先应该仔细考虑选择作为数据结构,因为从列表中间删除在列表长度中需要线性时间。如果您真的不需要随机访问第 k 个顺序项,也许可以考虑?listOrderedDict
0赞 max 5/5/2017
@GVelascoh为什么不创建,然后就在之前?newlist = []newlist.append(array[i])del array[i]
2赞 Ciro Santilli OurBigBook.com 6/6/2017
请注意,这可能在时间上效率低下:如果是链表,则随机访问成本高昂,如果是数组,则删除成本高昂,因为它们需要向前移动所有后续元素。一个体面的迭代器可以使链表实现变得有利。然而,这可以节省空间。list()list()
0赞 ingyhere 1/6/2021
@CiroSantilli郝海东冠状病六四事件法轮功 : pop(i) operation is still O(n).我将把存储效率放在 O(n) 的增量改进上,但我明白为什么有人会以不同的方式这样做。
8赞 Beefster 3/16/2018 #17

其他答案是正确的,从正在迭代的列表中删除通常是一个坏主意。反向迭代避免了一些陷阱,但遵循这样做的代码要困难得多,因此通常最好使用列表推导式或 .filter

但是,在一种情况下,从正在迭代的序列中删除元素是安全的:如果在迭代时只删除一个项目。这可以使用 或 .例如:returnbreak

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

当您对满足某些条件的列表中第一项执行一些具有副作用的操作,然后立即从列表中删除该项时,这通常比列表推导更容易理解。

1赞 CENTURION 8/23/2018 #18

对于任何有可能变得非常大的东西,我使用以下方法。

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

That should be significantly faster than anything else.

评论

0赞 Georgy 5/14/2019
From what I measured, NumPy starts to be faster for lists of more than 20 elements, and reaches >12x faster filtering for big lists of 1000 elements and more.
1赞 MathKid 9/22/2018 #19

In some situations, where you're doing more than simply filtering a list one item at time, you want your iteration to change while iterating.

Here is an example where copying the list beforehand is incorrect, reverse iteration is impossible and a list comprehension is also not an option.

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p
23赞 Mujeeb 10/23/2018 #20

Most of the answers here want you to create a copy of the list. I had a use case where the list was quite long (110K items) and it was smarter to keep reducing the list instead.

First of all you'll need to replace foreach loop with while loop,

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

The value of is not changed in the if block because you'll want to get value of the new item FROM THE SAME INDEX, once the old item is deleted.i

评论

1赞 anon01 12/11/2021
I don't want to like this but I do :)
0赞 tonysepia 4/9/2022
I think this is very creative! I would like to see more community input on this algorithm. It's easy to understand and appears to be overlooked by the contributors!
1赞 Mujeeb 4/10/2022
@tonysepia glad to see this solution is still helpful :)
0赞 tonysepia 4/10/2022
@Mujeeb oh Yes, you can see me using it in my algo here: stackoverflow.com/questions/71810348/…
1赞 Beefster 7/13/2023
This is not universally the smartest way to do it. There are certainly some cases where it's going to be your best option (e.g. tight memory constraints), but due to how many elements it has to shift around to remove the one element with a large list, it will likely be much slower than creating a new list with a filter. Though if you don't care about list order, you can copy the last element to the delete point and then delete the last element - and it will likely be much faster to do so than using a filter would be.
3赞 chseng 11/10/2018 #21

The most effective method is list comprehension, many people show their case, of course, it is also a good way to get an through .iteratorfilter

Filter receives a function and a sequence. applies the passed function to each element in turn, and then decides whether to retain or discard the element depending on whether the function return value is or .FilterTrueFalse

There is an example (get the odds in the tuple):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

Caution: You can also not handle iterators. Iterators are sometimes better than sequences.

评论

0赞 Supreet Sethi 8/10/2021
I probably think this is the most idiomatic way of removing the items from list. This behaviour will also be thread safe since your application is not mutating the variable.
1赞 Siddharth Satpathy 12/4/2018 #22

I can think of three approaches to solve your problem. As an example, I will create a random list of tuples . The condition that I choose is . In the final list we will only have those tuples whose sum is not equal to 15. somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]sum of elements of a tuple = 15

What I have chosen is a randomly chosen example. Feel free to change the list of tuples and the condition that I have chosen.

Method 1.> Use the framework that you had suggested (where one fills in a code inside a for loop). I use a small code with to delete a tuple that meets the said condition. However, this method will miss a tuple (which satisfies the said condition) if two consecutively placed tuples meet the given condition. del

for tup in somelist:
    if ( sum(tup)==15 ): 
        del somelist[somelist.index(tup)]

print somelist
>>> [(1, 2, 3), (3, 6, 6), (7, 8, 9), (10, 11, 12)]

Method 2.> Construct a new list which contains elements (tuples) where the given condition is not met (this is the same thing as removing elements of list where the given condition is met). Following is the code for that:

newlist1 = [somelist[tup] for tup in range(len(somelist)) if(sum(somelist[tup])!=15)]

print newlist1
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Method 3.> Find indices where the given condition is met, and then use remove elements (tuples) corresponding to those indices. Following is the code for that.

indices = [i for i in range(len(somelist)) if(sum(somelist[i])==15)]
newlist2 = [tup for j, tup in enumerate(somelist) if j not in indices]

print newlist2
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Method 1 and method 2 are faster than method 3. Method2 and method3 are more efficient than method1. I prefer method2. For the aforementioned example, time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7

1赞 Mark Zhang 4/16/2019 #23

If you will use the new list later, you can simply set the elem to None, and then judge it in the later loop, like this

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

In this way, you dont't need copy the list and it's easier to understand.

5赞 Mohideen bin Mohammed 4/30/2019 #24

A for loop will be iterate through an index...

Consider you have a list,

[5, 7, 13, 29, 65, 91]

You have used a list variable called . And you use the same to remove...lis

Your variable

lis = [5, 7, 13, 29, 35, 65, 91]
       0  1   2   3   4   5   6

during the 5th iteration,

Your number 35 was not a prime, so you removed it from a list.

lis.remove(y)

And then the next value (65) move on to the previous index.

lis = [5, 7, 13, 29, 65, 91]
       0  1   2   3   4   5

so the 4th iteration done pointer moved onto the 5th...

That’s why your loop doesn’t cover 65 since it’s moved into the previous index.

So you shouldn't reference a list into another variable which still references the original instead of a copy.

ite = lis # Don’t do it will reference instead copy

So do a copy of the list using .list[::]

Now you will give,

[5, 7, 13, 29]

The problem is you removed a value from a list during iteration and then your list index will collapse.

So you can try list comprehension instead.

Which supports all the iterable like, list, tuple, dict, string, etc.

评论

0赞 Hank Lenzi 6/3/2022
To put in a simpler way: do not iterate on the list you're trying to change. Iterate, instead, on the list with the items that have the criteria to be removed: I ran into this myself, and it was discussed here: stackoverflow.com/q/72478091/1973308lis = [5, 7, 13, 29, 35, 65, 91]not_primes = [35,65]for item in not_primes: if item in lis: lis.remove(item)
9赞 NoName 10/10/2019 #25

If you want to delete elements from a list while iterating, use a while-loop so you can alter the current index and end index after each deletion.

Example:

i = 0
length = len(list1)

while i < length:
    if condition:
        list1.remove(list1[i])
        i -= 1
        length -= 1

    i += 1