迭代时从列表中删除项目

我遍历Python中的元组列表,并试图删除它们,如果他们符合某些标准。

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

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

您可以使用列表理解来创build一个仅包含您不想删除的元素的新列表:

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

或者,通过指定片段somelist[:] ,可以改变现有列表以仅包含您想要的项目:

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

如果有其他引用somelist需要反映更改,这种方法可能会很有用。

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

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

或者在Python 3中:

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

build议列表parsing的答案几乎是正确的 – 除了他们build立一个全新的列表,然后给它与旧列表相同的名称,他们不修改旧的列表到位。 这与你在select性删除中所做的不同,就像在@ Lennart的build议中那样 – 它更快,但是如果你的列表是通过多个引用来访问的,那么事实上你只是重新引用其中一个引用而不是改变列表对象本身可能会导致微妙的,灾难性的错误。

幸运的是,获得列表parsing的速度以及就地变更所需的语义非常容易 – 只需要编码:

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

注意与其他答案的细微差别:这不是指定给一个裸号 – 它是分配给一个列表切片,恰好是整个列表,从而replace同一个Python列表对象内的列表内容 ,而不是仅仅重置一个参考(从以前的列表对象到新的列表对象)像其他答案一样。

您需要获取列表的副本,并首先对其进行迭代,否则迭代将失败,出现可能意想不到的结果。

例如(取决于什么types的列表):

 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 xrange(len(somelist) - 1, -1, -1): if some_condition(somelist, i): del somelist[i] 

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

对于这样的例子,你最好的办法是列表理解

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

如果你正在做一些比调用determine函数更复杂的事情,我宁愿创build一个新列表,并随时随地添加到列表中。 例如

 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) remove操作,使得这是O(n^2)algorithm。

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

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

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

要么

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

官方的Python 2教程4.2。 “对于陈述”说

如果您需要在循环内部修改正在迭代的序列(例如复制选定的项目),build议您首先进行复制。 迭代一个序列不会隐式地创build一个副本。 切片符号使这特别方便:

 >>> 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'] 

这是build议在: https : //stackoverflow.com/a/1207427/895245

Python 2文档7.3。 “for the statement”给出了相同的build议

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

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

Python可以做得更好吗?

看起来这个特定的Python API可以被改进。 比较它与Java对应的ListIterator ,它清楚地表明,除了迭代器本身,你不能修改被迭代的列表,并且给你提供了有效的方法去做,而不需要复制列表。 来吧,Python!

如果当前列表项目符合期望的标准,那么也可以创build新的列表。

所以:

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

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

 originalList[:] = newList 

注意,从Python文档:

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

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

我需要用一个巨大的清单来完成这个工作,重复清单似乎很昂贵,尤其是因为在我的情况下,与剩余的项目相比,删除项目的数量会很less。 我采取了这个低级的方法。

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

我不知道的是几个删除相比复制一个大的列表有多高效。 请评论,如果你有任何见解。

这个答案最初是为了回应一个已经被标记为重复的问题而编写的: 从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)仍在您的列表中? 事实certificate,修改列表,而使用循环迭代是一个非常坏的主意,没有特别的照顾。 (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)] 

正如你可以从这个结果推断出来的,每当条件语句的计算结果为真,并且列表项被移除时,循环的下一次迭代将跳过列表中下一项的计算,因为它的值现在位于不同的索引处。

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

 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)] 

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

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

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

 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)] 

最后,有一个更清洁的解决scheme,而不必做一个全新的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)] 

不幸的是,我无法充分描述reverse()是如何工作的。 当列表传递给它时,它返回一个“listreverseiterator”对象。 出于实际的目的,你可以把它看作是创build其论证的反转副本。 这是我推荐的解决scheme。

你可能想使用filter()作为内置的。

更多细节请点击这里

如果你想在迭代过程中做任何事情,那么同时得到索引(这可以保证你能够引用它,例如如果你有一个字典列表)和实际的列表项内容。

 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 ,以便你将要删除的指标不会改变你。

你可以尝试反向循环,所以对于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) 

这样索引是alignment的,不会受到列表更新的影响(无论你是否启动cur元素)。

我需要做类似的事情,在我的情况下,问题是内存 – 我需要合并一个列表中的多个数据集对象,做了一些东西后,作为一个新的对象,需要摆脱我正在合并的每个条目避免重复所有这些,炸毁记忆。 在我的情况下,在一个字典而不是一个列表中的对象工作正常:

“`

 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 

“`

一个可能的解决scheme,如果你不想使用理解,那么这个解决scheme很有用:

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

TLDR:

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

 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' 

如果可能的话,最好使用另一种方法,在迭代它的时候不需要修改迭代器,但是对于某些algorithm,它可能并不那么简单。 所以如果你确定你确实需要原始问题中描述的代码模式,那么这是可能的。

应该处理所有可变序列而不仅仅是列表。


完整答案:

编辑:在这个答案的最后一个代码示例给出了一个用例, 为什么你有时可能想修改一个列表,而不是使用列表理解。 答案的第一部分作为一个数组如何修改的教程。

解决scheme来自senderle的这个答案(对于一个相关的问题)。 这就解释了如何在遍历已被修改的列表时更新数组索引。 下面的解决scheme旨在正确跟踪数组索引,即使列表被修改。

从这里下载fluidIter.py https://github.com/alanbacon/FluidIterator ,它只是一个单独的文件,所以不需要安装git。 没有安装程序,所以你将需要确保文件是在你自己的Pythonpath。 该代码已经为python 3编写,并没有在python 2上testing。

 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] 

上面我们在stream体列表对象上使用了pop方法。 其他常见的迭代方法也被实现,如del fluidL[i].insert.append.extend 。 该列表也可以使用切片修改( sortreverse方法没有实现)。

唯一的条件是你只能修改列表,如果在任何时候fluidLl被重新分配给不同的列表对象,代码将不起作用。 原来的fluidL对象仍然会被for循环使用,但是会超出我们修改的范围。

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

如果我们想访问列表的当前索引值,我们不能使用枚举,因为这只会计算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类仅为原始列表对象提供了一个包装。 原始对象可以像stream体对象的属性那样访问,如下所示:

 originalList = fluidArr.fixedIterable 

更多的例子/testing可以在if __name__ is "__main__":fluidIter.py底部的if __name__ is "__main__":部分fluidIter.py 。 这些值得一看,因为他们解释了在各种情况下发生的事情。 如:使用切片replace大部分列表。 或者在嵌套for循环中使用(和修改)相同的迭代器。

正如我刚才所说的:这是一个复杂的解决scheme,会损害您的代码的可读性,使其更难debugging。 因此,其他解决scheme(如David Raznick的答案中提到的列表parsing)应首先考虑。 话虽如此,我已经find了这个类对我来说有用的地方,而且比追踪需要删除的元素的索引更容易使用。


编辑:正如在评论中提到的,这个答案并不真正存在这种方法提供解决scheme的问题。 我会尽力在这里解决:

列表parsing提供了一种生成新列表的方法,但是这些方法往往是孤立地看待每个元素,而不是整个列表的当前状态。

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

但是如果testFunc的结果依赖于已经添加到newList的元素呢? 或者仍然在可能被添加的oldList中的元素? 可能还有一种方法可以使用列表理解,但它会失去优雅,对于我来说,修改列表会更容易。

下面的代码是遭受上述问题的algorithm的一个例子。 该algorithm将减less列表,以便没有元素是任何其他元素的倍数。

 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] 

马上要创build列表的副本,以便在迭代和删除满足特定条件的列表中的元组时,可以将其作为参考。

然后,它取决于你想要输出的列表types,无论是被删除的元组列表还是未被删除的元组列表。

正如David所指出的那样,我推荐列表理解来保留你不想删除的元素。

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

你可以写这个

 for i, item in enumerate(my_list): if condition: my_list.pop(i) 

这里i是索引和item是内容。