在Python中循环列表并修改它

这段代码来自Python的文档。 我有点困惑。

words = ['cat', 'window', 'defenestrate'] for w in words[:]: if len(w) > 6: words.insert(0, w) print(words) 

以下是我首先想到的:

 words = ['cat', 'window', 'defenestrate'] for w in words: if len(w) > 6: words.insert(0, w) print(words) 

为什么这段代码会创build一个无限循环,而第一个代码不会呢?

这是一个难题! python,可以逃脱初学者。

在这里, words[:]是魔法酱。

注意:

 >>> words = ['cat', 'window', 'defenestrate'] >>> words2 = words[:] >>> words2.insert(0, 'hello') >>> words2 ['hello', 'cat', 'window', 'defenestrate'] >>> words ['cat', 'window', 'defenestrate'] 

而现在没有[:]

 >>> words = ['cat', 'window', 'defenestrate'] >>> words2 = words >>> words2.insert(0, 'hello') >>> words2 ['hello', 'cat', 'window', 'defenestrate'] >>> words ['hello', 'cat', 'window', 'defenestrate'] 

这里需要注意的是, words[:]返回现有列表的一个copy ,因此您正在迭代一个未修改的副本。

你可以检查你是否使用id()引用相同的列表:

在第一种情况下:

 >>> words2 = words[:] >>> id(words2) 4360026736 >>> id(words) 4360188992 >>> words2 is words False 

在第二种情况下:

 >>> id(words2) 4360188992 >>> id(words) 4360188992 >>> words2 is words True 

值得注意的是, [i:j]被称为切片运算符 ,它所做的是返回从索引i开始到(但不包括)索引j的新列表副本。

所以, words[0:2]给你

 >>> words[0:2] ['hello', 'cat'] 

省略起始索引意味着它默认为0 ,而忽略最后一个索引意味着它默认为len(words) ,最终结果是你收到整个列表的副本。


如果你想让你的代码更具可读性,我推荐使用copy模块。

 from copy import copy words = ['cat', 'window', 'defenestrate'] for w in copy(words): if len(w) > 6: words.insert(0, w) print(words) 

这基本上和你的第一个代码片断一样,而且更具可读性。

或者(如注释中的DSM所述)和python> = 3,您也可以使用words.copy()来执行相同的操作。

words[:]words所有元素复制到新列表中。 所以当你迭代words[:] ,你实际上遍历了当前所有words的元素。 所以当你修改words ,这些修改的效果在words[:]是不可见的(因为在开始修改words之前你调用了words[:]

在后面的例子中,您正在迭代words ,这意味着您对words任何更改对您的迭代器words都是可见的。 因此,当你插入到索引0的words ,你会用一个索引“碰撞”每个其他元素。 所以当你继续下一个迭代时,你会在下一个words索引处得到元素,但这只是你刚刚看到的元素(因为你在列表的开头插入了一个元素,将所有其他元素向上移动一个索引)。

要看到这一点,请尝试以下代码:

 words = ['cat', 'window', 'defenestrate'] for w in words: print("The list is:", words) print("I am looking at this word:", w) if len(w) > 6: print("inserting", w) words.insert(0, w) print("the list now looks like this:", words) print(words) 

(除了@速速答案)

看下面的例子:

 words = ['cat', 'window', 'defenestrate'] words2 = words words2 is words 

结果: True

这意味着名字和words2是指同一个对象。

 words = ['cat', 'window', 'defenestrate'] words2 = words[:] words2 is words 

结果: False

在这种情况下,我们创build了新的对象。

我们来看看迭代器和迭代器:

iterable是一个对象,它有一个返回一个迭代器的__iter__方法,或者定义了一个可以从零开始的连续索引(并且在索引不再有效时引发IndexError__getitem__方法。 所以一个迭代器是一个你可以从中获取迭代器的对象。

迭代器是具有next (Python 2)或__next__ (Python 3)方法的对象。

iter(iterable)返回迭代器对象,而list_obj[:]返回一个新的列表对象,即list_object的精确副本。

在你的第一个案例中:

 for w in words[:] 

for循环将迭代列表的新副本而不是原始单词。 任何词的改变对循环迭代都没有影响,循环正常结束。

循环是如何工作的:

  1. 循环在迭代器上调用iter方法并遍历迭代器

  2. 循环调用迭代器对象的next方法来获取来自迭代器的下一个项目。 重复此步骤,直到没有剩余元素

  3. 当引发StopIterationexception时,循环终止。

在你的第二种情况下:

 words = ['cat', 'window', 'defenestrate'] for w in words: if len(w) > 6: words.insert(0, w) print(words) 

您正在迭代原始列表中的单词,并将单词添加到单词对迭代器对象有直接影响。 所以每当你的单词更新时,相应的迭代器对象也被更新,因此创build一个无限循环。

看这个:

 >>> l = [2, 4, 6, 8] >>> i = iter(l) # returns list_iterator object which has next method >>> next(i) 2 >>> next(i) 4 >>> l.insert(2, 'A') >>> next(i) 'A' 

每次在StopIteration之前更新原始列表,都会得到更新后的迭代器,并相应地返回next返回值。 这就是为什么你的循环无限运行。

有关更多的迭代和迭代协议,你可以看看这里 。