2017-06-19 308 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