2017-06-19 309 views
54

此代码是从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) 

为什么这个代码创建一个无限循环,第一个不?

+1

因为你在每次迭代时都会在单词列表中插入一个元素)) – marmeladze

+8

第一个是最初的“单词”而不是“单词”本身的副本 – depperm

+14

在第一个中,你迭代了一个在开始向它添加东西之前拍摄的“词”的副本。在第二个,你通过'words'试图循环,使'同时words'长,所以你永远不会到达终点。 – khelwood

回答

75

这是陷阱之一!蟒蛇,可以逃脱初学者。

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]被称为切片运算符,它所做的是返回一个新的t他列出从指数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(),它可以做同样的事情。

+9

@当然速度 - 你可以把它写成单词[:] = [如果len(w)> 6] [:: - 1] +单词“w用于单词w ...... –

+9

Jon,我做了提到“更多可读性”,不能少于...:P –

+0

@速度更多可读性:'单词[:0] = [如果len(w)> 6],则用w表示w。 – wizzwizz4

3

(除了@Coldspeed回答)

请看下面的例子:

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

结果:True

这意味着名称wordwords2指代相同的对象。

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

结果:False

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

10

words[:]words中的所有元素复制到一个新列表中。所以当你迭代words[:]时,你实际上正在遍历words当前所有的元素。所以,当你修改words,这些修改的影响,在words[:]是不可见的

在后面的示例(因为你开始修改words之前words[:]称呼),你迭代words,这意味着你做任何更改到words是你的迭代器确实可见。因此,当您插入到words的索引0中时,您会将words中的每个其他元素都“撞上”一个索引。因此,当您继续进行for循环的下一次迭代时,您会在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) 
0

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

可迭代是具有__iter__方法,该方法返回一个对象 迭代器,或者定义了一个__getitem__方法,该方法可以从0开始连续索引为 (并且当 索引不再有效时引发IndexError)。因此,一个迭代是一个对象,你 可以从一个迭代器。

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

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

在你第一种情况:

for w in words[:] 

for循环会遍历列表中没有原话的新副本。单词中的任何更改对循环迭代都没有影响,并且循环正常结束。

这是循环如何完成其​​工作:

  1. 循环调用迭代和迭代的迭代器iter方法

  2. 循环调用迭代器对象next方法从迭代器获取下一个项目。重复该步骤,直到没有更多的元素时留下一个StopIteration引发异常

  3. 循环终止。

在你的第二个案例:

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

你迭代的初始列表的单词和添加元素的话有iterator对象有直接的影响。所以每次更新单词时,相应的迭代器对象也会更新,因此会创建一个无限循环。

看看这个:

>>> 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回报。这就是你的循环无限运行的原因。

更多关于迭代和迭代的协议,你可以看看here