在迭代时修改列表 - 为什么不呢?

时间:2012-05-30 08:01:35

标签: python list

几乎每个关于这个主题的教程和SO答案都坚持你在迭代时不应该修改列表,但是如果代码有效,我不明白为什么这是一件坏事。例如:

while len(mylist) > 0:
    print mylist.pop()

我错过了什么吗?

4 个答案:

答案 0 :(得分:13)

while len(mylist) > 0:
    print mylist.pop()

您没有在列表上进行迭代。你每次都在检查一个原子状态。

此外:

while len(mylist) > 0:

可以改写为:

while len(mylist):

可以改写为:

while mylist:

答案 1 :(得分:8)

为什么你不应该在迭代时修改列表的原因是,例如,你正在迭代20个数字的列表,如果你点击偶数,你将它从列表中弹出并继续直到你有一个奇数列表。

现在,假设这是您的示例数据[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],并开始迭代它。第一次迭代,数字为1,因此您继续,以下数字为2,因此您将其弹出,然后冲洗并重复。您现在感觉应用程序正常工作,因为结果列表是[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

现在假设您的示例数据为[1, 2, 4, 5, 7, 8, 10, 11, 12, 13, 15, 15, 17, 18, 20],并且您运行与以前相同的代码段,并在迭代原始列表时进行变更。您的结果列表为[1, 4, 5, 7, 10, 11, 13, 15, 15, 17, 20],这显然不正确,因为列表中仍包含偶数。

如果你打算在迭代它的同时改变列表

for elem in lst:
    # mutate list in place

您应该将其更改为

for elem in lst[:]:
    # mutate list in place

[:]语法创建一个新列表,它是原始列表的精确副本,这样您就可以愉快地改变原始列表,而不会影响您正在处理的内容,因为您不会有任何意外的方面 - 改变你正在迭代的列表的影响。

如果您的列表相当大,而不是创建一个新列表并单步执行它,请查看使用生成器表达式,或者如果您觉得有必要,请为列表编写自己的生成器,以免浪费内存和CPU周期。 / p>

答案 2 :(得分:7)

我将详细介绍为什么你不应该迭代列表。当然,我的意思是

for elt in my_list:
    my_list.pop()

或类似的习语。

首先,我们需要考虑Python的for循环的作用。由于您可以尝试迭代任何对象,因此Python不一定知道如何迭代您提供的任何内容。因此,有一个列表(heh)它试图解决如何逐个呈现值的事情。它首先做的是检查对象上的__iter__方法,如果存在,则调用它。

此调用的结果将是可迭代的对象;也就是说,一个next方法。现在我们很高兴:只需反复拨打next,直到StopIteration被提升为止。

为什么这很重要?好吧,因为__iter__方法实际上要查看数据结构以查找值,并记住一些内部状态,以便它知道下一步要查看的位置。但是如果你改变了数据结构,那么__iter__就无法知道你一直在摆弄,所以它会轻率地继续尝试获取新数据。这在实践中意味着您可能会跳过列表中的元素。


通过查看源代码来证明这种说法是很好的。来自listobject.c

static PyObject *
listiter_next(listiterobject *it)
{
    PyListObject *seq;
    PyObject *item;

    assert(it != NULL);
    seq = it->it_seq;
    if (seq == NULL)
        return NULL;
    assert(PyList_Check(seq));

    if (it->it_index < PyList_GET_SIZE(seq)) {
        item = PyList_GET_ITEM(seq, it->it_index);
        ++it->it_index;
        Py_INCREF(item);
        return item;
    }

    Py_DECREF(seq);
    it->it_seq = NULL;
    return NULL;
}

特别注意它确实模拟了一个C风格的for循环,其中it->it_index扮演了索引变量的一部分。特别是,如果您从列表中删除某个项目,则不会更新it_index,因此您可以跳过某个值。

答案 3 :(得分:4)

您的代码不会遍历列表。

for i in mylist:
  print mylist.pop()
相关问题