標籤:

為什麼python中不建議在for循環中修改列表?

Python中,為什麼說for是一種遍歷列表的有效方式,但是不建議在for循環中修改列表,否則將導致python難以跟蹤其中的元素?python會在for循環中跟蹤列表的元素嗎?跟蹤做什麼?為什麼又建議在while循環中修改列表,是因為while循環不會跟蹤列表元素嗎?初學python,勿噴


直接上代碼演示。

對這個list進行操作。

a = [1, 2, 3, 4, 5, 6]

首先,將裡面值為3的元素修改為4。我們可以寫成這樣。

for i in range(len(a)):
if a[i] == 3:
a[i] = 4

或者這樣

for i, v in enumerate(a):
if v == 3:
a[i] = 4

結果變成了這樣:

a = [1, 2, 4, 4, 5]

好像用for循環完成得挺順利的,但是列表的修改,不僅限於對值得修改,還有插入和刪除。

接著上一步,我們現在要嘗試刪除裡面所有值為4的元素。

for i, v in enumerate(a):
if v == 4:
del a[i]

然而,得到卻是

a = [1, 2, 4, 5]

如果寫成這樣

for i in range(len(a)):
if a[i] == 4:
del a[i]

python直接就報錯了

Traceback (most recent call last):
File "&", line 2, in &
IndexError: list index out of range

由於在遍歷的過程中,刪除了其中一個元素,導致後面的元素整體前移,導致有個元素成了漏網之魚。同樣的,在遍歷過程中,使用插入操作,也會導致類似的錯誤。這也就是問題里說的無法「跟蹤」元素。

如果使用while,則可以在面對這樣情況的時候靈活應對。同樣是上面那個例子。

i = 0
while i &< len(a): if a[i] == 4: del a[i] else: i += 1

----------------------------------------------------------------------

for循環還是很好用的,但是涉及到列表的插入刪除,還是不要用了。


樓上@mouser 已經從iterator的角度解釋清楚了這個問題為何會出現:在修改過程中,原list的長度可能會發生變化,導致for-in循環的遍歷不夠「到位」(不越界、不漏值)。

小白來補充一點如何解決這個問題吧~

As the Python tutorial (https://docs.python.org/3/tutorial/controlflow.html#for-statements) suggests, "If you need to modify the sequence you are iterating over while inside the loop (for example to duplicate selected items), it is recommended that you first make a copy." You can copy a list by calling the list constructor or slicing the list from the beginning to the end.
——UCB CS 61A Sp17 第三次project

翻譯過來:把原有的list複製一下,遍歷拷貝的list、但修改原有的list。

比如Python的list循環遍歷中,刪除數據的正確方法 - bananaplan - 博客園 提供了一個例子:

num_list = [1, 2, 3, 4, 5]
print(num_list)
for item in num_list[:]:
if item == 2: num_list.remove(item)
else:
print(item)
print(num_list)

(附)複製列表的方法:

lst = [1,2,3]
lst1 = lst[:] # one way
lst2 = list(lst) # another

?(^?^*)啦啦


本質上,這是個 for xxx in 可迭代對象: 的問題

拿這個舉例

for i in range(5):

在程序第一次運行到這句的時候,

python會自動去調用range(5)對象的__iter__()方法,返回一個range_iterator對象

再由這個range_iterator對象不斷調用其__next__()方法,直到捕獲異常StopIteration為止

完成迭代

換句話說,當執行這個for語句的時候,迭代次數就已經被in後面的可迭代對象確定下來了

至於通過__next__()方法返回的值是怎麼和i產生關係的,好像不屬於這個問題

當已經被確定了迭代(循環)次數後

在列表沒有贈刪元素時,當然不會有越界的危險

但一旦在迭代過程中pop()或者append()元素後

前者越界

後者漏值


「扁平結構比嵌套結構更好」 – 《Python之禪》

比如 list(map(lambda x:4 if x==3 else x,a))

如果一定要用for:

a = [1, 2, 3, 4, 5, 6]

3變成4

[4 if x==3 else x for x in a]

b = [1, 2, 4, 4, 5, 6]

刪除4

[x for x in b if x != 4]

也是可以的。。


推薦閱讀:

Python技術分享的亂象
Python 黑帽編程大綱(變化中)
Python爬蟲實戰六之抓取愛問知識人問題並保存至資料庫

TAG:Python |