【问题标题】:Python2.7 - list.remove(item) within a loop gives unexpected behaviuor [duplicate]Python2.7 - 循环中的 list.remove(item) 给出了意外的行为 [重复]
【发布时间】:2015-11-09 17:03:39
【问题描述】:

我想删除列表中的所有偶数。但是有件事让我很困惑... 这是代码。

lst = [4,4,5,5]

for i in lst:
    if i % 2 == 0:
        print i
        lst.remove(i)

print lst

它打印 [4, 5, 5] 为什么不打印 [5, 5]?

【问题讨论】:

  • 您在迭代列表时正在修改列表。这会干扰迭代。

标签: python python-2.7


【解决方案1】:

应该是这样的

for i in lst[:]:
    if i % 2 == 0:
        print i
        lst.remove(i)

print lst

问题:

您在迭代列表时正在修改列表。因此迭代在完成之前就停止了

解决方案:

你可以遍历列表的副本

你可以使用list comprehension

lst=[i for i in lst if i%2 != 0]

【讨论】:

  • @Sam 很乐意提供帮助
【解决方案2】:

通过使用list.remove,您正在迭代期间修改列表。这会中断迭代,给您带来意想不到的结果。

一种解决方案是使用filter 或列表推导式创建一个新列表:

>>> filter(lambda i: i % 2 != 0, lst)
[5, 5]
>>> [i for i in lst if i % 2 != 0]
[5, 5]

如果需要,您可以将任一表达式分配给 lst,但您无法避免使用这些方法创建新的列表对象。

【讨论】:

    【解决方案3】:

    其他答案已经提到您在迭代列表时正在修改列表,并提供了更好的方法来做到这一点。我个人更喜欢列表推导方法:

    odd_numbers = [item for item in numbers if item % 2 != 0]
    

    对于您指定的非常小的列表,我肯定会这样做。

    但是,这确实会创建一个新列表,如果您有一个非常大的列表,这可能会成为问题。在整数的情况下,大可能至少意味着数百万,但准确地说,它需要多大才能开始给您带来内存使用问题。在这种情况下,这里有几种方法可以做到这一点。

    一种方式类似于您问题中代码的意图。您遍历列表,同时删除偶数。但是,为避免修改您正在迭代的列表可能导致的问题,您向后迭代。有很多方法可以向前迭代,但这更简单。

    这是使用while 循环的一种方法:

    # A one hundred million item list that we don't want to copy
    # even just the odd numbers from to put into a new list.
    numbers = range(100000000)  # list(range(100000000)) in Python 3
    
    index = len(numbers) - 1  # Start on the index of the last item
    while index >= 0:
        if numbers[index] % 2 == 0:
            numbers.pop(index)
        index -= 1
    

    这是使用for 循环的另一种方式:

    # A one hundred million item list that we don't want to copy
    # even just the odd numbers from to put into a new list.
    numbers = range(100000000)  # list(range(100000000)) in Python 3
    
    for index in xrange(len(numbers) - 1, -1, -1):  # range(...) in Python 3
        if numbers[index] % 2 == 0:
            numbers.pop(index)
    

    注意在while 循环和for 循环版本中,我使用了numbers.pop(index),而不是numbers.remove(numbers[index])。首先,.pop() 效率更高,因为它提供了索引,而.remove() 必须在列表中搜索第一次出现的值。其次,请注意我所说的“值的第一次出现”。这意味着除非每个项目都是唯一的,否则使用 .remove() 将删除与循环当前所在的项目不同的项目,这最终会将当前项目留在列表中。

    我想再提一个解决方案,适用于您需要保留原始列表但又不想使用太多内存来存储奇数副本的情况。如果您只想对奇数进行一次迭代(或者您非常讨厌使用内存,以至于您宁愿在需要时重新计算事物),您可以使用生成器。这样做可以让您遍历列表中的奇数,而无需任何额外的内存,除了生成器机制使用的无关紧要的数量。

    生成器表达式的定义与列表推导式完全相同,只是它用括号而不是方括号括起来:

    odd_numbers = (item for item in numbers if item % 2 != 0)
    

    请记住,生成器表达式正在对原始列表进行迭代,因此在迭代过程中更改原始列表会给您带来与在for 循环中迭代列表时修改列表相同的问题。事实上,生成器表达式本身就是使用了for 循环。

    顺便说一句,生成器表达式不应该只用于非常大的列表;每当我不需要一次计算整个列表时,我都会使用它们。

    总结/TLDR:

    “最佳”方式完全取决于您在做什么,但这应该涵盖很多情况。

    假设列表是“小”或“大”:

    如果您的列表很小,请使用列表推导式(如果可以的话,甚至使用生成器表达式)。如果它很大,请继续阅读。

    如果您不需要原始列表,请使用while 循环或for 循环方法完全删除偶数(尽管使用.pop(),而不是.remove())。如果您确实需要原始列表,请继续阅读。

    如果您只对奇数进行一次迭代,请使用生成器表达式。如果您多次迭代它们,但您愿意重复计算以节省内存,请使用生成器表达式。

    如果您对奇数的迭代次数过多以至于每次都重新计算它们,或者您需要随机访问,那么使用列表推导式创建一个仅包含奇数的新列表。这将使用大量内存,但它们是中断。

    【讨论】:

    • @Sam,不客气。不要忘记为好的答案投票(不仅仅是我的),并接受对你最有帮助的答案(即使它不是我的:P)。这是the SO way
    【解决方案4】:

    作为一般原则,您不应在迭代集合时对其进行修改。这会导致某些元素的跳过,并在某些情况下导致索引错误。

    与其从列表中删除元素,不如创建另一个具有相同名称的引用会更容易。它的时间复杂度也较低。

    lst = filter(lambda i: i % 2 !=0, lst)
    

    【讨论】:

    • 是的,这很正常。如果有帮助,请将其标记为正确答案
    猜你喜欢
    • 2018-12-31
    • 1970-01-01
    • 2021-03-02
    • 1970-01-01
    • 2013-01-28
    • 2013-05-30
    • 1970-01-01
    • 2011-12-01
    • 1970-01-01
    相关资源
    最近更新 更多