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

lfa*_*one 917 python iteration

我正在迭代Python中的元组列表,并且如果它们符合某些条件,我会尝试删除它们.

for tup in somelist:
    if determine(tup):
         code_to_remove_tup
Run Code Online (Sandbox Code Playgroud)

我应该用什么代替code_to_remove_tup?我无法弄清楚如何以这种方式删除项目.

Dav*_*ick 786

您可以使用列表推导来创建仅包含您不想删除的元素的新列表:

somelist = [x for x in somelist if not determine(x)]
Run Code Online (Sandbox Code Playgroud)

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

somelist[:] = [x for x in somelist if not determine(x)]
Run Code Online (Sandbox Code Playgroud)

如果有其他参考somelist需要反映更改,则此方法可能很有用.

你也可以使用而不是理解itertools.在Python 2中:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)
Run Code Online (Sandbox Code Playgroud)

或者在Python 3中:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)
Run Code Online (Sandbox Code Playgroud)

  • 如果我的名单庞大且无法复制,该怎么办? (11认同)
  • @jpcgt你应该使用`somelist [:] =(x表示某些列表中的x,如果确定(x))`这将创建可能不会创建任何不必要副本的生成器. (11认同)
  • @RostislavKondratenko:`list_ass_slice()`函数实现`somelist [:] =`calls [`PySequence_Fast()`](https://docs.python.org/3/c-api/sequence.html#c.PySequence_Fast )内部.此函数始终返回一个列表,即[@Alex Martelli的解决方案已经使用列表而不是生成器更有效率](http://stackoverflow.com/a/1208792/4279) (7认同)
  • 您是否愿意解释将列表理解分配给列表和列表克隆之间的区别?两种方法都不会改变原始列表“ somelist”吗? (6认同)
  • @Bowen Liu,如果之前有其他对该列表的引用,则不会。比较以下两个示例:`a = [1, 2, 3]; b = a; a = [i for i in a if i > 1]; 打印(一);print(b)` 和 `a = [1, 2, 3]; b = a; a[:] = [i for i in a if i > 1]; 打印(一);打印(b)`。 (5认同)
  • 如果你知道只有少数会被删除,你可以加快速度吗,即只删除那些并将其他内容留在原地而不是重写它们? (3认同)

Ale*_*lli 565

建议列表推导的答案几乎是正确的 - 除了它们构建一个全新的列表然后给它与旧列表相同的名称,它们不会修改旧的列表.这与你通过选择性删除所做的不同,就像在@ Lennart的建议中一样 - 它更快,但是如果你的列表是通过多个引用访问的,那么你只是重新安装其中一个引用而不是改变列表对象本身可能导致微妙的,灾难性的错误.

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

somelist[:] = [tup for tup in somelist if determine(tup)]
Run Code Online (Sandbox Code Playgroud)

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

  • @Derek`x = ['foo','bar','baz']; y = x; x = [如果确定(项目),则为x中的项目项目);`这将'x`重新分配给列表理解的结果,但是`y`仍然引用_original_列表`['foo','bar','巴兹']`.如果你期望`x`和`y`引用相同的列表,你可能已经引入了bug.你可以通过分配整个列表的切片来防止这种情况,如Alex所示,我在这里显示:`x = ["foo","bar","baz"]; y = x; x [:] = [项目中的项目,如果确定(项目)];`.该列表已就地修改.确保对列表的所有引用(这里都是"x"和"y")引用新列表. (58认同)
  • @Paul:由于dicts是无序的,因此切片对于dicts来说毫无意义.如果你想用dict`b`的内容替换dict`a`的内容,请使用`a.clear(); a.update(B)`. (10认同)

Len*_*bro 276

您需要获取列表的副本并首先迭代它,否则迭代将失败,结果可能是意外结果.

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

for tup in somelist[:]:
    etc....
Run Code Online (Sandbox Code Playgroud)

一个例子:

>>> 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
[]
Run Code Online (Sandbox Code Playgroud)

  • 对于阅读此内容的人来说,这对于列表来说非常慢.`remove()`必须遍历每个迭代的整个列表,因此它将需要永远. (29认同)
  • Zen#3,Simple比复杂更好.得到我的投票! (17认同)
  • @Zen因为第二个遍历列表的副本.因此,在修改原始列表时,不会修改迭代的副本. (13认同)
  • 与列表(somelist)相比,做一些列表[:]有什么好处? (3认同)
  • `list(somelist)`将一个iterable转换为一个列表.`somelist [:]`制作一个支持切片的对象的副本.所以他们不一定做同样的事情.在这种情况下,我想制作`somelist`object的副本,所以我使用`[:]` (3认同)
  • 处理仅十几个项目的列表时,大O时间并不重要。对于将来的程序员而言,通常清晰,简单的理解远比性能有价值。 (3认同)
  • 这是官方教程中提到的方法:http://stackoverflow.com/a/34238688/895245 (2认同)
  • @navin和两倍的记忆! (2认同)

Joh*_*hin 116

for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]
Run Code Online (Sandbox Code Playgroud)

你需要倒退,否则就像锯掉你所坐的树枝一样:-)

Python的2个用户:更换range通过xrange避免产生硬编码列表

  • reversed()不会创建新列表,它会在提供的序列上创建反向迭代器.与enumerate()一样,你必须将它包装在list()中以实际获取它的列表.你可能会想到sorted(),*每次都会*创建一个新的列表(必须这样,所以它可以对它进行排序). (14认同)
  • 在Python的最新版本中,您可以使用`reversed()`builtin来更干净地完成这项工作 (11认同)
  • 对于数组,这是 O(N*M),如果从大列表中删除许多项目,它会非常慢。所以不推荐。 (2认同)
  • @SamWatkins是的,此答案适用于从非常大的数组中删除几个元素的情况。较少的内存使用,但是可能会慢m倍。 (2认同)
  • 喜欢这个答案和砍树枝的比喻!只要您不需要做任何复杂的事情,列表理解就可以很好地工作。唯一的评论是,我会在 python2 中使用 `reversed(xrange(len(somelist)))` ,在 python3 中使用 `reversed(range(len(somelist)))` (2认同)

Eli*_*ght 48

这样一个例子的最佳方法是列表理解

somelist = [tup for tup in somelist if determine(tup)]
Run Code Online (Sandbox Code Playgroud)

如果您正在做一些比调用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
Run Code Online (Sandbox Code Playgroud)

使用复制列表remove可能会使您的代码看起来更清晰,如下面的答案之一所述.绝对不应该为非常大的列表执行此操作,因为这涉及首先复制整个列表,并且还对O(n) remove要删除的每个元素执行操作,使其成为O(n^2)算法.

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


Cir*_*四事件 48

官方的Python 2教程4.2."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']
Run Code Online (Sandbox Code Playgroud)

这是建议的:https://stackoverflow.com/a/1207427/895245

Python 2里的文档7.3."for声明"给出了同样的建议:

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

for x in a[:]:
    if x < 0: a.remove(x)
Run Code Online (Sandbox Code Playgroud)

Python能做得更好吗?

似乎可以改进这个特定的Python API.例如,将它与Java对应的ListIterator进行比较,这清楚地表明除了迭代器本身之外你不能修改被迭代的列表,并且无需复制列表就可以提供有效的方法.来吧,Python!

  • 最后有人指出了实际的文档。在这之前我无法理解任何答案。 (2认同)

Cid*_*ide 37

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

somelist[:] = filter(lambda tup: not determine(tup), somelist)
Run Code Online (Sandbox Code Playgroud)

要么

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))
Run Code Online (Sandbox Code Playgroud)


Mic*_*ael 12

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

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

我不知道的是,将一些删除与复制大型列表相比有多高效.如果您有任何见解,请评论.

  • 注意这可能是时间效率低的:如果list()是一个链表,则随机访问是昂贵的;如果list()是一个数组,则删除是昂贵的,因为它们需要将所有后续元素向前移动。体面的迭代器可以使链表实现更好。但是,这可以节省空间。 (2认同)

ntk*_*tk4 10

如果当前列表项符合所需条件,也可以智能地创建新列表.

所以:

for item in originalList:
   if (item != badValue):
        newList.append(item)
Run Code Online (Sandbox Code Playgroud)

并避免必须使用新列表名称重新编码整个项目:

originalList[:] = newList
Run Code Online (Sandbox Code Playgroud)

请注意,来自Python文档:

copy.copy(x)返回x的浅表副本.

copy.deepcopy(x)返回x的深层副本.

  • 这不会增加几年前未接受的新信息. (3认同)
  • 这很简单,只是另一种查看问题的方法@MarkAmery.对于那些不喜欢压缩编码语法的人来说,它不那么简洁. (2认同)

小智 9

这个答案最初是为了回答一个已被标记为重复的问题而编写的: 从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)
Run Code Online (Sandbox Code Playgroud)

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

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>
Run Code Online (Sandbox Code Playgroud)

啊哈!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))
Run Code Online (Sandbox Code Playgroud)

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

L1 is now: [(1, 2), (5, 6), (1, -2)]
Run Code Online (Sandbox Code Playgroud)

为什么(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)]
Run Code Online (Sandbox Code Playgroud)

正如您可以从该结果推断的那样,每次条件语句的计算结果为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)
Run Code Online (Sandbox Code Playgroud)

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

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]
Run Code Online (Sandbox Code Playgroud)

这是因为当我们创建L2时,python实际上并没有创建新对象.相反,它仅将L2引用到与L1相同的对象.我们可以用'is'来验证它,这与仅仅是"equals"(==)不同.

>>> L2=L1
>>> L1 is L2
True
Run Code Online (Sandbox Code Playgroud)

我们可以使用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)]
Run Code Online (Sandbox Code Playgroud)

最后,有一个更清洁的解决方案,而不是制作一个全新的L1副本.reverse()函数:

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)]
Run Code Online (Sandbox Code Playgroud)

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


Bee*_*ter 8

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

然而,在一种情况下,从正在迭代的序列中删除元素是安全的:如果您在迭代时仅删除一项。这可以使用 areturn或 a来确保break。例如:

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

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


Muj*_*eeb 8

这里的大多数答案都希望您创建列表的副本。我有一个用例,其中列表很长(110K 项),而继续减少列表会更明智。

首先,您需要用 while 循环替换 foreach 循环

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1
Run Code Online (Sandbox Code Playgroud)

iif 块中的值不会更改,因为一旦删除旧项目,您将希望从相同的索引中获取新项目的值。


NoN*_*ame 6

如果要在迭代时从列表中删除元素,请使用 while 循环,以便在每次删除后更改当前索引和结束索引。

例子:

i = 0
length = len(list1)

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

    i += 1
Run Code Online (Sandbox Code Playgroud)


fan*_*ous 5

如果您想在迭代期间执行其他操作,那么获取索引(这保证您能够引用它,例如,如果您有一个字典列表)和实际列表项内容可能会很好。

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]
Run Code Online (Sandbox Code Playgroud)

enumerate让您可以立即访问该项目和索引。reversed这样您稍后要删除的索引就不会改变。


Ale*_*xey 5

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

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
Run Code Online (Sandbox Code Playgroud)


Moh*_*med 5

for循环将迭代索引...

\n

假设你有一个清单,

\n
[5, 7, 13, 29, 65, 91]\n
Run Code Online (Sandbox Code Playgroud)\n

您使用了一个名为 的列表变量lis。你用同样的方法来删除...

\n

你的变量

\n
lis = [5, 7, 13, 29, 35, 65, 91]\n       0  1   2   3   4   5   6\n
Run Code Online (Sandbox Code Playgroud)\n

在第 5 次迭代期间,

\n

您的数字 35不是素数,因此您将其从列表中删除。

\n
lis.remove(y)\n
Run Code Online (Sandbox Code Playgroud)\n

然后下一个值 (65)移动到上一个索引。

\n
lis = [5, 7, 13, 29, 65, 91]\n       0  1   2   3   4   5\n
Run Code Online (Sandbox Code Playgroud)\n

所以第四次迭代完成指针移到第五次......

\n

这就是为什么你的循环不覆盖 65,因为它移到了上一个索引中。

\n

因此,您不应该将列表引用到另一个仍然引用原始而不是副本的变量中。

\n
ite = lis # Don\xe2\x80\x99t do it will reference instead copy\n
Run Code Online (Sandbox Code Playgroud)\n

因此,使用 制作列表的副本list[::]

\n

现在你将给予,

\n
[5, 7, 13, 29]\n
Run Code Online (Sandbox Code Playgroud)\n

问题是您在迭代期间从列表中删除了一个值,然后您的列表索引将崩溃。

\n

所以你可以尝试列表理解

\n

它支持所有可迭代的,如列表、元组、字典、字符串等。

\n