我在Python中迭代一个元组列表,并试图删除它们,如果它们满足某些条件。

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

我应该用什么来代替code_to_remove_tup?我不知道怎么用这种方式把东西拿掉。


当前回答

这个答案最初是为了回答一个问题而写的,这个问题后来被标记为重复: 在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)

第一个问题是同时向remove()传递'a'和'b',但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'对象。出于实际目的,您可以将其视为创建其参数的反向副本。这是我推荐的解决方案。

其他回答

如果您想在迭代期间做其他事情,那么最好同时获得索引(这保证您能够引用它,例如,如果您有一个字典列表)和实际的列表项内容。

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使您可以同时访问项和索引。反向是为了以后你要删除的索引不会改变。

对于这样的示例,最好的方法是列表理解

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

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

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

对于任何有潜力做大的东西,我使用以下方法。

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)

这应该比其他任何东西都快得多。

我需要做一些类似的事情,在我的情况下,问题是内存——我需要在一个列表中合并多个数据集对象,在对它们做了一些事情之后,作为一个新对象,并且需要摆脱我正在合并的每个条目,以避免重复所有它们并增加内存。在我的情况下,对象在一个字典而不是一个列表工作得很好:

```

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

```

其他答案是正确的,从你正在迭代的列表中删除通常是一个坏主意。反向迭代避免了一些陷阱,但是要遵循这样做的代码要困难得多,所以通常您最好使用列表理解或过滤器。

然而,有一种情况下,从您正在迭代的序列中删除元素是安全的:如果您在迭代时只删除一项。这可以通过返回或中断来确保。例如:

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

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