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

我正在遍历 Python 中的元组列表,并尝试在满足特定条件的情况下将其删除。

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

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

答案

somelist = [x for x in somelist if not determine(x)]
somelist[:] = [x for x in somelist if not determine(x)]
from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)
from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)
somelist[:] = [tup for tup in somelist if determine(tup)]
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
[]
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]
somelist = [tup for tup in somelist if determine(tup)]
newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist
for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)

官方 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()必须迭代整个列表以查找值。

最佳解决方法

要么:

通常,除非内存是一个大问题,否则默认情况下,您只想默认使用更快的.append()选项。

Python 可以做得更好吗?

似乎可以改进此特定的 Python API。例如,将其与:

两者都清楚地表明,除了使用迭代器本身之外,您无法修改要迭代的列表,并为您提供了无需复制列表即可进行修改的有效方法。

也许基本理由是,Python 列表被假定为动态数组支持,并且因此任何类型的去除将是时间低效反正,同时 Java 有既具有更好的接口层次结构ArrayListLinkedList的实现方式ListIterator

Python stdlib 中似乎也没有明确的链接列表类型: Python 链接列表

somelist[:] = filter(lambda tup: not determine(tup), somelist)
from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))
array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1
for item in originalList:
   if (item != badValue):
        newList.append(item)
originalList[:] = newList
>>> 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)
>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>
# 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)]
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)]
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=L1
>>> L1 is L2
True
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 = [(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)]