簡體   English   中英

在迭代時修改列表 - 為什么不呢?

[英]Modifying a list while iterating over it - why not?

幾乎關於這個主題的每個教程和SO答案都堅持你在迭代它時不應該修改列表,但是如果代碼有效,我不明白為什么這是一件壞事。 例如:

while len(mylist) > 0:
    print mylist.pop()

我錯過了什么嗎?

while len(mylist) > 0:
    print mylist.pop()

你沒有迭代列表。 你每次都在檢查原子狀況。

也:

while len(mylist) > 0:

可以改寫為:

while len(mylist):

可以改寫為:

while mylist:

為什么你不應該在迭代時修改列表的原因是,例如,你正在迭代20個數字的列表,如果你點擊偶數,你將它從列表中彈出並繼續直到你有一個列表奇怪的數字。

現在,說這是你的樣本數據[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] ,然后你開始迭代它。 第一次迭代,數字為1所以你繼續,下面的數字是2所以你把它彈出,然后沖洗並重復。 您現在感覺應用程序正常工作,因為結果列表是[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

現在假設您的樣本數據是[1, 2, 4, 5, 7, 8, 10, 11, 12, 13, 15, 15, 17, 18, 20]並且您運行與以前相同的代碼並進行變異迭代它時的原始列表。 您的結果列表是[1, 4, 5, 7, 10, 11, 13, 15, 15, 17, 20] ,這顯然是不正確的,因為列表中仍然包含偶數。

如果你打算改變列表,同時迭代它

for elem in lst:
    # mutate list in place

你應該把它改成

for elem in lst[:]:
    # mutate list in place

[:]語法創建一個新列表,它是原始列表的精確副本,這樣您就可以愉快地改變原始列表而不會影響您正在處理的內容,因為您不會因改變列表而產生任何意外的副作用你正在迭代。

如果您的列表相當大,而不是創建一個新列表並單步執行它,請查看使用生成器表達式,或者如果您覺得有必要,請為列表編寫自己的生成器,以免浪費內存和CPU周期。

我將詳細介紹為什么不應該迭代列表。 當然,我的意思是

for elt in my_list:
    my_list.pop()

或類似的習語。

首先,我們需要考慮Python的for循環。 由於您可以嘗試迭代任何對象,因此Python不一定知道如何迭代您提供的任何對象。 因此,有一個列表(heh)它試圖解決如何逐個呈現值的事情。 它首先做的是檢查對象上的__iter__方法,如果存在,則調用它。

這個調用的結果將是一個可迭代的對象; 也就是說,有一個next方法。 現在我們很高興:只需重復調用next ,直到StopIteration被提升。

為什么這很重要? 好吧,因為__iter__方法實際上要查看數據結構以查找值,並記住一些內部狀態,以便它知道下一步要查看的位置。 但是如果你改變數據結構,那么__iter__無法知道你一直在擺弄,所以它將繼續嘗試抓住新數據。 這在實踐中意味着您可能會跳過列表中的元素。


通過查看源代碼來證明這種說法是很好的。 來自listobject.c

static PyObject *
listiter_next(listiterobject *it)
{
    PyListObject *seq;
    PyObject *item;

    assert(it != NULL);
    seq = it->it_seq;
    if (seq == NULL)
        return NULL;
    assert(PyList_Check(seq));

    if (it->it_index < PyList_GET_SIZE(seq)) {
        item = PyList_GET_ITEM(seq, it->it_index);
        ++it->it_index;
        Py_INCREF(item);
        return item;
    }

    Py_DECREF(seq);
    it->it_seq = NULL;
    return NULL;
}

特別注意它確實模擬了一個C風格的for循環, it->it_index扮演索引變量的一部分。 特別是,如果從列表中刪除項目,則不會更新it_index ,因此您可以跳過一個值。

您的代碼不會遍歷列表。

for i in mylist:
  print mylist.pop()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM