什麼是Python的迭代器和生成器?(附程式碼)

THU資料派發表於2020-06-18

迭代器:一次一個!

Python 是一種美麗的程式語言。我喜歡它提供的靈活性和難以置信的功能。我喜歡深入研究Python的各種細微差別,並瞭解它如何應對不同的情況。

在使用Python的過程中,我瞭解到了一些功能,這些功能的使用與其簡化的複雜度不相稱。我喜歡稱它們為Python中“隱藏的寶石”。很多人對此並不瞭解,但對於分析和資料科學專家來說,它們非常有用。

Python迭代器和生成器正好屬於這一類。它們的潛力是巨大的!

什麼是Python的迭代器和生成器?(附程式碼)

如果你曾經在處理大量資料時遇到麻煩(誰沒有呢?!),並且計算機記憶體不足,那麼你會喜歡Python中的迭代器和生成器的概念。 

與其將所有資料一次性都放入記憶體中,不如將它按塊處理,只處理當時所需的資料,對嗎?這將大大減少我們計算機記憶體的負載。這就是迭代器和生成器的作用!

因此,讓我們仔細讀讀本文,探索Python迭代器和生成器的世界吧。

我假設你熟悉Python的基礎知識。如果沒有,我建議你先從下面的熱門課程學起:

Python資料科學

https://courses.analyticsvidhya.com/courses/introduction-to-data-science?utm_source=blog&utm_medium=python-iterators-and-generators

這是我們要介紹的內容:

  • 什麼是可迭代物件?
  • 什麼是Python迭代器?
  • 在Python中建立一個迭代器
  • 熟悉Python中的生成器
  • 實現Python中的生成器表示式
  • 為什麼你應該使用迭代器?

什麼是可迭代物件?

可迭代物件是能夠一次返回其一個成員的物件”。

通常使用for迴圈完成此操作。像列表、元組、集合、字典、字串等等之類的物件被稱為可迭代物件。簡而言之,任何你可以迴圈的物件都是可迭代物件。

我們可以使用for迴圈逐個地返回可迭代的元素。在這裡,我們使用for迴圈遍歷列表的元素:什麼是Python的迭代器和生成器?(附程式碼)

既然我們知道了什麼是可迭代物件,那麼實際上我們是如何遍歷這些值的?以及我們的迴圈如何知道何時停止?進入到迭代器部分!

什麼是Python迭代器?

迭代器是代表資料流的物件,即可迭代。它們在Python中實現了迭代器協議。這是什麼?

好吧,迭代器協議允許我們在一個可迭代物件中使用兩種方法來迴圈遍歷項:__iter __()和__next __()。所有的可迭代物件和迭代器都有__iter __()方法,該方法返回一個迭代器。

迭代器跟蹤可迭代物件的當前狀態。

但可迭代物件和迭代器不同之處在於__next __()方法只能由迭代器訪問。這使得無論何時只要我們要求迭代器返回下一個值,迭代器就會返回下一個值。

讓我們建立一個簡單的可迭代物件、本例中為一個列表以及使用__iter __()方法來構造一個迭代器來了解其工作什麼是Python的迭代器和生成器?(附程式碼)什麼是Python的迭代器和生成器?(附程式碼)是的,正如我所說,可迭代物件有用於建立迭代器的__iter __()方法,但它們沒有僅迭代器才有的__next __()方法。因此,讓我們再試一次,然後嘗試從列表中檢索值:

什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

完美!但等一下,我不是說迭代器也具有__iter __()方法嗎?那是因為迭代器也是可迭代的,但反過來不成立。它們是自己的迭代器。讓我透過遍歷迭代器向你展示這個概念:什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

酷!但我們可以使用iter()和next()來代替__iter__()和__next__()方法,它們提供了一種更簡潔的方法:

什麼是Python的迭代器和生成器?(附程式碼)

但如果我們超過了呼叫next()方法的限制次數,該怎麼辦?這會發生什麼呢?什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

是的,我們得到了一個錯誤!如果我們在到達迭代器的末尾之後嘗試訪問下一個值,則會引起StopIteration異常,該異常的意思是“你不能更進一步了!”。

我們可以使用異常處理來處理此錯誤。實際上,我們可以自己構建一個迴圈來遍歷可迭代的項:

sample = ['statistics', 'linear algebra', 'probability']  
it = iter(sample)  
while True:      
    # this will execute till an error is raised      
    try:          
        val = next(it)      
    # when we reach end of the list, error is raised and we break out of the loop      
    except StopIteration:          
        break      
    print(val) 

什麼是Python的迭代器和生成器?(附程式碼)如果你退後一步,你會意識到,這正是for迴圈在底層執行的方式。我們在此處手動迴圈中所做的操作,for迴圈會自動執行相同的操作。這就是為什麼for迴圈比遍歷可迭代物件更可取,因為它們會自動處理異常。

每當我們迭代一個可迭代物件時,for迴圈透過iter()知道要迭代的項,並使用next()方法返回後續的項。

在Python中建立一個迭代器

既然我們知道了Python迭代器是如何工作的,我們可以更深入地研究並從頭開始建立一個迭代器,以更好地瞭解其是如何湊效的。 

我將建立一個用於列印所有偶數的簡單迭代器:什麼是Python的迭代器和生成器?(附程式碼)讓我們分解一下這段Python程式碼:

  • __init __()方法是類建構函式,呼叫類時會首先執行該函式。它用於分配程式執行期間類最初所需的任何值。我在這裡設定num變數的初始值為2;
  • iter()和next()方法使這個類變成了迭代器;
  • iter()方法返回迭代器物件並對迭代進行初始化。由於類物件本身是迭代器,因此它返回自身;
  • next()方法從迭代器中返回當前值,並改變下一次呼叫的狀態。我們將num變數的值加2,因為我們只列印偶數。

我們可以建立Sequence物件來遍歷Sequence類,在該物件上呼叫next()方法:什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

我沒有寫sequence結束的條件,因此迭代器將永遠繼續返回下一個值。但我們可以使用停止條件輕鬆地對其進行更新:什麼是Python的迭代器和生成器?(附程式碼)

我剛剛加入了一條if語句,只要值超過10,該語句就會停止迭代:什麼是Python的迭代器和生成器?(附程式碼)什麼是Python的迭代器和生成器?(附程式碼)

在這裡,我沒有使用next()方法從迭代器返回值,而是使用了for迴圈,該迴圈的工作方式與之前相同。

熟悉Python中的生成器

生成器也是迭代器,但更加優雅。使用生成器,我們可以實現與迭代器相同的功能,但不必在類中編寫iter()和next()函式。相反,我們可以使用一個簡單的函式來完成與迭代器相同的任務:

什麼是Python的迭代器和生成器?(附程式碼)

你是否注意到這個生成器函式和常規函式的不同?是的,yield關鍵字!

普通函式使用return關鍵字返回值。但是生成器函式使用yield關鍵字返回值。這就是生成器函式與常規函式不同的地方(除了這種區別,它們是完全相同的)。

yield關鍵字的工作方式類似於普通的return關鍵字,但有額外的功能:它能記住函式的狀態。因此,下次呼叫generator函式時,它不是從頭開始,而是從上次呼叫中停止的位置開始。

讓我們看看它是如何工作的:什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

生成器屬於“生成器”型別,它是迭代器的一種特殊型別,但仍然是迭代器,因此它們也是懶惰的工作者。除非next()方法明確要求它們這樣做,否則它們不會返回任何值。

最初建立fib()生成器函式的物件時,它會初始化prev和curr變數。現在,當在物件上呼叫next()方法時,生成器函式會計算值並返回輸出,同時記住函式的狀態。因此,下次呼叫next()方法時,該函式將從上次停止的地方開始,從那裡繼續。

每當使用next()方法時,該函式將繼續生成值,直到prev變得大於5,這時將引起StopIteration異常,如下所示:

什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

實現Python中的生成器表示式

你不必在每次執行生成器時都編寫函式。相反,你可以使用生成器表示式,就像列表生成式一樣。唯一的區別是,與列表生成式不同,生成器表示式包含在圓括號內,如下所示:什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

但它們仍然很懶,因此你需要使用next()方法。但你現在知道使用for迴圈可以更好地返回值:什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

當你編寫簡單的程式碼時,生成器表示式非常有用,因為它們易讀、易理解。但隨著程式碼變得更復雜,它們的功能會迅速變弱。在這種情況下,你發現自己會重新使用生成器函式,生成器函式在編寫更復雜的函式方面提供了更大的靈活性。

為什麼你應該使用迭代器?

一個重要的問題:為什麼要先考慮用迭代器?

我在文章開頭提到了這一點:之所以使用迭代器,是因為它們為我們節省了大量記憶體。這是因為迭代器在生成時不會計算項,而只會在呼叫它們時計算。 

如果我建立一個包含1000萬個項的列表,並建立一個包含相同數量項的生成器,則它們記憶體大小上的差異將令人震驚:什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

 對於相同的數量的項,列表和生成器在記憶體大小上存在巨大差異。這就是迭代器的美。 

不僅如此,你可以使用迭代器逐行讀取檔案中的文字,而不是一次性讀取所有內容。這會再次為你節省大量記憶體,尤其是在檔案很大的情況下。

在這裡,讓我們使用生成器來迭代讀取檔案。為此,我們可以建立一個簡單的生成器表示式來懶惰地開啟檔案,一次讀取一行:

什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

這很棒,但對於資料科學家或分析師而言,他們最終都要在Pandas的 dataframe中處理大型資料集。當你不得不處理龐大的資料集時,也許這個資料集有幾千行資料點甚至更多。如果Pandas可以解決這一難題,那麼資料科學家的生活將變得更加輕鬆。

好吧,你很幸運,因為Pandas的read_csv()(https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html)有處理該問題的chunksize引數。它使你可以按指定大小的塊來載入資料,而不是將整個資料載入到記憶體中。處理完一個資料塊後,可以對dataframe物件執行next()方法來載入下一個資料塊。就這麼簡單!

我將讀取Black Friday資料集(https://datahack.analyticsvidhya.com/contest/black-friday/?utm_source=blog&utm_medium=python-iterators-and-generators),該資料集包含550,068行資料,讀取時設定每塊的大小為10,這樣做只是為了演示該函式的用法:什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

什麼是Python的迭代器和生成器?(附程式碼)

很有用,不是嗎?

結語

我確信你現在已經習慣於使用迭代器,而且一定在考慮把所有函式轉換為生成器!你開始喜歡Python程式設計的強大之處。 

你以前使用過Python迭代器和生成器嗎?或者你要與社群分享其他“隱藏的寶石”?大家可以在下方評論! 

原文標題:

What are Python Iterators and Generators? Programming Concepts Every Data Science Professional Should Know

原文連結:

https://www.analyticsvidhya.com/blog/2020/05/python-iterators-and-generators/ 

相關文章