成為Python大牛必須要掌握的高階語法——yield

qq1622479435發表於2018-12-21

1. 什麼是yield

在介紹yield語法之前,首先要向大家說明Python中的迭代(iteration)、可迭代(iterable)、迭代器(iterator)以及生成器(Generator)的概念:

迭代是一種對資料的操作,例如針對一個list逐一獲取其中的元素的過程就叫做迭代。而可迭代是物件的一種特性,迭代操作只能針對擁有可迭代特性的物件進行,常見的可迭代物件包括陣列、元組、字典等資料集合,下面程式碼給大家演示了一個基本的迭代過程:

迭代器也是一種可迭代物件,與普通的可迭代物件的區別在於,迭代器內部實現了next函式用來生成每次迭代迴圈需要返回的元素。而最後的生成器則又是一種特殊的迭代器,具體體現上就是使用yield語法的函式,講到這裡就提到了yield語法,總的來說yield就是用來產生一個生成器的語法,例如將上述的迭代過程修改為生成器方式可以這樣寫:

1240

迭代器也是一種可迭代物件,與普通的可迭代物件的區別在於,迭代器內部實現了next函式用來生成每次迭代迴圈需要返回的元素。而最後的生成器則又是一種特殊的迭代器,具體體現上就是使用yield語法的函式,講到這裡就提到了yield語法,總的來說yield就是用來產生一個生成器的語法,例如將上述的迭代過程修改為生成器方式可以這樣寫:

1240

上述程式碼的my_generator()即返回了一個生成器物件,每次迴圈時執行到yield處即返回當時的index的值,到下一次迴圈時將從上次返回的yield處繼續執行,直到index的值不滿足小於5的條件時結束整個函式,此時也結束了對這個生成器的迭代過程。

這四者之間的關係可能會稍微有些混亂,再給大家簡單的總結一下:生成器是一種特殊的迭代器,而迭代器又是一種特殊的可迭代物件,可迭代物件就是可以執行迭代操作也就是可以通過for迴圈來遍歷的物件。

本文福利:私信小編【PDF】可獲取小編精心整理的電子書一套

2. 為什麼要使用yield

看了上述兩個迭代過程,大家可能有些疑問,使用yield改造成生成器方式的程式碼看起來比簡單的迭代一個列表的方式要複雜許多,那麼這樣寫有什麼優勢呢?

首先,使用yield語法的生成器最主要的一個優勢就是極其省記憶體。例如上述兩個迭代過程,同樣是遍歷輸出0-4這幾個元素,使用列表的方式需要構建出一個長度為5的陣列並儲存在記憶體中,而使用生成器的方式只需要一個index變數即可實現,這還是迭代元素較少的情況下,如果迭代的是100萬甚至1000萬個元素時,列表的方式就需要構建一個長度為100萬或者1000萬的陣列,這時對於記憶體的使用就是非常大的負擔了,而使用生成器的方式,無論是迭代100萬還是1000萬個元素,依然只需要一個index變數即可實現。

並且生成器的方式是即用即計算的,即迭代到對應的元素時,這個元素才相應的計算生成出來,而列表的方式需要在迭代開始前就構建出整個迭代陣列,這在某些情況下可以極大地節省計算時間。例如下面這段程式碼:

1240

.在學習中有迷茫不知如何學習的朋友小編推薦一個學Python的學習q u n   227  -435-  450可以來了解一起進步一起學習!免費分享視訊資料

這段程式碼中,實際的迭代過程只進行到第10個元素即退出了整個迴圈,但是在迭代開始前,依然要計算1000萬次來生成迭代列表,這就造成了大量的計算和記憶體資源。而如果通過生成器重寫該迭代過程的話:

1240

生成器在迭代開始前並不會計算出所有需要迭代的值,只有用到時才會計算相應的值並返回,因此上述程式碼的index將只會計算到10即結束了整個迭代過程,避免了計算和記憶體資源的浪費。

3. yield語法示例1:DIY一個range函式

Python自帶的range函式可以產生一個可迭代物件,常用於for迴圈中,在Python 2中range函式生成的是一個列表,而在Python 3中range函式生成的是一個生成器。現在讓我們來通過yield語法DIY一個自己的range生成器吧!

我們首先構造一個返回給定範圍陣列的函式:

1240

這個函式接受兩個int型別的引數,分別為陣列的開始和結束,每個數之間間隔為1,我們還可以通過增加一個引數來指定兩個數之間的間隔,實現函式更高的靈活性:

1240

我們先來執行測試一下這個range函式:

1240

上述程式碼的輸出結果如下:

2

4

6

輸出結果符合我們的預期,現在通過yield語法來將我們自己DIY的range函式改造成一個生成器:

1240

改造起來也非常簡單,首先將定義的用來儲存迭代元素的列表刪除,然後將原來新增元素到列表中的程式碼改造成yield start即可,這樣我們就自己DIY了一個簡易的、基於生成器實現的range函式。

4. yield語法示例2:讀取檔案–《告白氣球》

生成器除了可以用於計算生成數字元素外,在IO讀取方面也能起到很大作用,例如在讀取一個超大檔案,或者查詢某個返回結果超多的資料庫時,使用通過yield語法構造的生成器來完成讀取操作可以很大程度上降低程式對於記憶體的佔用。

例如我們有一個名為my_file.txt的檔案,裡面儲存了周董的《告白氣球》的歌詞,現在我們可以通過yield語法來構造一個生成器用於一行一行的讀取每一句歌詞:

1240

這裡使用with語法來讀取檔案,這是Python 3推薦的方式。file.readline()函式每次返回一行內容,由於返回的內容帶有每行結尾的換行符,因此通過line.strip(‘ ’)將換行符過濾掉。每次通過yield返回一行內容之後,再次通過file.readline()函式獲取下一行內容,直到整個檔案被完全迭代。

讓我們來執行測試一下這個按行讀取檔案內容的生成器:

1240

上述程式碼的輸出結果如下:

塞納河畔 左岸的咖啡

我手一杯 品嚐你的美

留下脣印 的嘴

……

《告白氣球》的歌詞就一行一行的輸出到螢幕上了,由於歌詞行數過多,因此這邊只複製出前三行給大家演示結果。

5. yield語法示例3:斐波那契數列

斐波那契數列是一道經典的演算法題,也是程式設計師面試時經常會被問到的一道題。斐波那契數列的就是一個形如1, 1, 2, 3, 5, 8, ……的數列,從第三項開始,每一項都等於前兩項之和。使用Python來實現一個計算斐波那契數列的典型函式如下:

1240

這個函式通過一個名為fib_list的陣列儲存生成的前n個斐波那契數,最後一次性返回整個陣列。其中a, b = b, a + b是Python的一個特色用法,用於快速交換兩個數,相當於:

1240

參考之前DIY的range函式的寫法,將這個計算斐波那契數列的函式通過yield語法修改為生成器:

1240

讓我們來測試執行一下這個通過yield語法實現的斐波那契數列生成器:

1240

對應的輸出結果為:

1

1

2

3

5

可以看到,從第三項開始的每一項都是前兩項的和,這樣的輸出結果就是我們要的斐波那契數列。


相關文章