几乎每个关于这个主题的教程和SO答案都坚持你在迭代时不应该修改列表,但是如果代码有效,我不明白为什么这是一件坏事。例如:
while len(mylist) > 0:
print mylist.pop()
我错过了什么吗?
答案 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()