在python中迭代和更新列表[重复]

Posted

技术标签:

【中文标题】在python中迭代和更新列表[重复]【英文标题】:Iterating and Updating the list in python [duplicate] 【发布时间】:2017-12-17 22:55:04 【问题描述】:

我无法理解为什么以下代码会无限期运行 循环(当我不使用副本列表时)

list = ["Mohit","kumar","sffsfshfsd"]
for w in list:
    if(len(w)) > 5:
        list.insert(0,w)
    print("inside loop")

print(list)  

上面的代码无限期地打印inside loop

现在,如果代替列表,我使用下面的副本列表可以正常工作。

list = ["mohit","kumar","sffffgssddf"]

for w in list[:]:
    if len(w) > 5:
        list.insert(0,w)
    print("inside loop")

print(list)  

现在我在 python 文档中读到这是行为 我会得到,但我想了解其背后的原因。谢谢在 前进。

【问题讨论】:

旁注:永远不要在内置函数之后命名变量(例如list)。 谢谢,会记住这一点@mpf82 一旦达到"sffsfshfsd" 每次迭代都会添加到列表的前面 您的列表在您对其进行迭代时不断增长,因此它永远不会停止。 但是我已经到了列表的第三个元素,为什么我们要在插入新元素之后循环回到第一个元素? 【参考方案1】:

第一个 for 循环 for w in list 将使用迭代器(来自 iter(list))来检索和循环列表中的每个项目。此迭代器不会立即获取整个列表 - 它是惰性,这意味着它在需要时一次只从列表中获取一个项目。您可以了解the iteration protocol here,或iteration/generators and laziness here。

循环遍历索引 0 和 1 什么都不做,因为它们的字符串长度小于 6。但是,在索引 2 处,您将 "sffsfshfsd" 添加到 list 的开头。现在list 已经增长,并且在索引 3 中有一些东西:"sffsfshfsd"。然后迭代继续,从下一个索引 (3) 中选择值,该值再次在开头添加,将索引 3 处的相同值移动到索引 4...循环永远不会结束。

在您的第二个循环w in list[:] 中,您创建整个列表 (by using a slice operator) 的副本 并对其进行迭代。您正在将项目添加到原始列表,而不是副本,因此迭代器不会触及您添加的项目。

PS:我试图搜索 Python 源代码(它是 C)来证明列表迭代器实际上使用递增索引(如上所述)。我对阅读 Python 的源代码并不精通,但这是我在cpython/listobject.c 中找到的内容:

Iterator creation, sets starting index to 0

2797 static PyObject *
2798 list_iter(PyObject *seq)
2799 
....
2806     it = PyObject_GC_New(listiterobject, &PyListIter_Type);
....
2809     it->it_index = 0;
....
2813     return (PyObject *)it;
2814 

next uses it->it_index from above and then increments it

2831 static PyObject *
2832 listiter_next(listiterobject *it)
2833 
....
2844         item = PyList_GET_ITEM(seq, it->it_index);
2845         ++it->it_index;
....
2847         return item;
....
2853 

在我看来是合法的?

【讨论】:

【参考方案2】:

为了模拟列表迭代在内部的工作方式,让我们使用整数索引和while 循环重写您的程序。

lst = ["Mohit", "kumar", "sffsfshfsd"]
pos = 0
while pos < len(lst):
  word = lst[pos]
  print('lst=%s pos=%d word=%s' % (lst, pos, word))
  if len(word) > 5:
    lst.insert(0, word)
  pos += 1

下面显示了当你运行它时会发生什么:

lst=['Mohit', 'kumar', 'sffsfshfsd'] pos=0 word=Mohit
lst=['Mohit', 'kumar', 'sffsfshfsd'] pos=1 word=kumar
lst=['Mohit', 'kumar', 'sffsfshfsd'] pos=2 word=sffsfshfsd
lst=['sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd'] pos=3 word=sffsfshfsd
lst=['sffsfshfsd', 'sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd'] pos=4 word=sffsfshfsd
lst=['sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd'] pos=5 word=sffsfshfsd
lst=['sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd'] pos=6 word=sffsfshfsd
lst=['sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd'] pos=7 word=sffsfshfsd
lst=['sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd'] pos=8 word=sffsfshfsd
lst=['sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd'] pos=9 word=sffsfshfsd
...

(这种情况一直持续到您耗尽内存或耐心为止。)

如您所见,您不断将最后的 'sffsfshfsd' 移动到右侧,因此您的代码会一直查看它并且永不停止。

如果您在副本上工作,则不会发生这种情况,因为您不再修改您正在迭代的列表。

如果您在插入后调整循环索引也不会发生:

  if len(word) > 5:
    lst.insert(0, word)
    pos += 1  # account for the extra word
  pos += 1

或移动单词而不是复制它:

  if len(word) > 5:
    lst.insert(0, lst.pop(pos))  # don't change len(lst)

【讨论】:

【参考方案3】:

发生这种情况是因为您在从第三次开始的每次迭代中都将“sffsfshfsd”附加到列表中,因此列表永远不会结束。

【讨论】:

【参考方案4】:

在第一个代码中,您在循环的同一个列表中插入元素。这就是为什么它一直在内循环,因为列表无限增长。 在第二个代码中,您正在制作副本,将 for 循环和原始列表分开,因此它最终会停止。

【讨论】:

【参考方案5】:

引用from the docs:

注意:当序列被 循环(这只能发生在可变序列,即列表)。一个 内部计数器用于跟踪接下来使用哪个项目,并且 这在每次迭代时递增。当这个计数器到达 循环终止的序列的长度。这意味着,如果 套件从序列中删除当前(或前一个)项目, 将跳过下一个项目(因为它获取当前项目的索引 已经处理过)。同样,如果套件插入 在当前项目之前的序列中的项目,当前项目将是 下次通过循环再次处理。这可能会导致令人讨厌的 可以通过使用切片创建临时副本来避免的错误 整个序列,例如,

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

Python 中列表的 for 循环在内部维护一个计数器,用于获取下一项。

在您的第一个代码中,当它到达 sffsfshfsd(即索引 2)时,您再次将其插入到列表的开头,因此所有项目移动一个位置,现在 sffsfshfsd 将移动到索引 3 并被拾取在下一次迭代中。然后继续……

在您的第二个代码中,您正在迭代列表的副本,并且在您修改原始列表时不会修改列表的副本。

lst = ["Mohit","kumar","sffsfshfsd"]
for i, w in enumerate(lst):
    print("Index: i | List: list".format(i=i, list=lst))
    if(len(w)) > 5:
        lst.insert(0, w)

输出:

Index: 0 | List: ['Mohit', 'kumar', 'sffsfshfsd']
Index: 1 | List: ['Mohit', 'kumar', 'sffsfshfsd']
Index: 2 | List: ['Mohit', 'kumar', 'sffsfshfsd']
Index: 3 | List: ['sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd']
Index: 4 | List: ['sffsfshfsd', 'sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd']
Index: 5 | List: ['sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd']
Index: 6 | List: ['sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'sffsfshfsd', 'Mohit', 'kumar', 'sffsfshfsd']

【讨论】:

【参考方案6】:

我认为这是一个非常有趣的问题。我相信答案应该出现在 python 源代码实现中(抱歉我找不到它,希望专家可以指导我们使用 Python 实现)

for 循环不会创建原始数据的副本。因此,每次添加新数据时,循环都会继续。 (我不确定for循环是如何在实现层面实现的,我相信它可能会使用迭代器)

另一方面[:],这个操作符将创建一个原始数据集的新副本。因此,无论您如何更改原始数据集,for 循环都会在副本上循环(不会更改)。

证明如下:

list = ["mohit","kumar","sffffgssddf"]
test = list
list.append("test")
print test 
#['mohit', 'kumar', 'sffffgssddf', 'test']

#clear data, let's try [:]
list = ["mohit","kumar","sffffgssddf"]
test = list[:]
list.append("test")
print test 
#['mohit', 'kumar', 'sffffgssddf']

因此,在您的第二个示例中很清楚,您的 for 循环正在循环原始数据的副本。因此原始数据集更改不会影响复制数据。因此,您的第二个示例正在运行,第一个示例将无限循环。

希望对你有帮助。

【讨论】:

非常感谢@White。而其他人大多解决了我问题的第一部分。你也清除了第二部分。多谢 。 :) @Manya 不客气。等待专家给我们展示Python内核,这将是确定的答案。

以上是关于在python中迭代和更新列表[重复]的主要内容,如果未能解决你的问题,请参考以下文章

迭代Python列表中的每两个元素[重复]

迭代时从Python列表中删除项目[重复]

在python中一次迭代列表的两个值[重复]

迭代二维python列表[重复]

(Python)列表索引超出范围-迭代[重复]

迭代列表时更改列表的最佳方法[重复]