1. 什么是yield
在介紹yield語(yǔ)法之前,首先要向大家說(shuō)明Python中的迭代(iteration)、可迭代(iterable)、迭代器(iterator)以及生成器(Generator)的概念:
迭代是一種對(duì)數(shù)據(jù)的操作,例如針對(duì)一個(gè)list逐一獲取其中的元素的過程就叫做迭代。而可迭代是對(duì)象的一種特性,迭代操作只能針對(duì)擁有可迭代特性的對(duì)象進(jìn)行,常見的可迭代對(duì)象包括數(shù)組、元組、字典等數(shù)據(jù)集合,下面代碼給大家演示了一個(gè)基本的迭代過程:
迭代器也是一種可迭代對(duì)象,與普通的可迭代對(duì)象的區(qū)別在于,迭代器內(nèi)部實(shí)現(xiàn)了next函數(shù)用來(lái)生成每次迭代循環(huán)需要返回的元素。而最后的生成器則又是一種特殊的迭代器,具體體現(xiàn)上就是使用yield語(yǔ)法的函數(shù),講到這里就提到了yield語(yǔ)法,總的來(lái)說(shuō)yield就是用來(lái)產(chǎn)生一個(gè)生成器的語(yǔ)法,例如將上述的迭代過程修改為生成器方式可以這樣寫:
迭代器也是一種可迭代對(duì)象,與普通的可迭代對(duì)象的區(qū)別在于,迭代器內(nèi)部實(shí)現(xiàn)了next函數(shù)用來(lái)生成每次迭代循環(huán)需要返回的元素。而最后的生成器則又是一種特殊的迭代器,具體體現(xiàn)上就是使用yield語(yǔ)法的函數(shù),講到這里就提到了yield語(yǔ)法,總的來(lái)說(shuō)yield就是用來(lái)產(chǎn)生一個(gè)生成器的語(yǔ)法,例如將上述的迭代過程修改為生成器方式可以這樣寫:
上述代碼的my_generator()即返回了一個(gè)生成器對(duì)象,每次循環(huán)時(shí)執(zhí)行到y(tǒng)ield處即返回當(dāng)時(shí)的index的值,到下一次循環(huán)時(shí)將從上次返回的yield處繼續(xù)執(zhí)行,直到index的值不滿足小于5的條件時(shí)結(jié)束整個(gè)函數(shù),此時(shí)也結(jié)束了對(duì)這個(gè)生成器的迭代過程。
這四者之間的關(guān)系可能會(huì)稍微有些混亂,再給大家簡(jiǎn)單的總結(jié)一下:生成器是一種特殊的迭代器,而迭代器又是一種特殊的可迭代對(duì)象,可迭代對(duì)象就是可以執(zhí)行迭代操作也就是可以通過for循環(huán)來(lái)遍歷的對(duì)象。
2. 為什么要使用yield
看了上述兩個(gè)迭代過程,大家可能有些疑問,使用yield改造成生成器方式的代碼看起來(lái)比簡(jiǎn)單的迭代一個(gè)列表的方式要復(fù)雜許多,那么這樣寫有什么優(yōu)勢(shì)呢?
首先,使用yield語(yǔ)法的生成器最主要的一個(gè)優(yōu)勢(shì)就是極其省內(nèi)存。例如上述兩個(gè)迭代過程,同樣是遍歷輸出0-4這幾個(gè)元素,使用列表的方式需要構(gòu)建出一個(gè)長(zhǎng)度為5的數(shù)組并存儲(chǔ)在內(nèi)存中,而使用生成器的方式只需要一個(gè)index變量即可實(shí)現(xiàn),這還是迭代元素較少的情況下,如果迭代的是100萬(wàn)甚至1000萬(wàn)個(gè)元素時(shí),列表的方式就需要構(gòu)建一個(gè)長(zhǎng)度為100萬(wàn)或者1000萬(wàn)的數(shù)組,這時(shí)對(duì)于內(nèi)存的使用就是非常大的負(fù)擔(dān)了,而使用生成器的方式,無(wú)論是迭代100萬(wàn)還是1000萬(wàn)個(gè)元素,依然只需要一個(gè)index變量即可實(shí)現(xiàn)。
并且生成器的方式是即用即計(jì)算的,即迭代到對(duì)應(yīng)的元素時(shí),這個(gè)元素才相應(yīng)的計(jì)算生成出來(lái),而列表的方式需要在迭代開始前就構(gòu)建出整個(gè)迭代數(shù)組,這在某些情況下可以極大地節(jié)省計(jì)算時(shí)間。例如下面這段代碼:
.在學(xué)習(xí)中有迷茫不知如何學(xué)習(xí)的朋友小編推薦一個(gè)學(xué)Python的學(xué)習(xí)q u n? ?227? -435-? 450可以來(lái)了解一起進(jìn)步一起學(xué)習(xí)!免費(fèi)分享視頻資料
這段代碼中,實(shí)際的迭代過程只進(jìn)行到第10個(gè)元素即退出了整個(gè)循環(huán),但是在迭代開始前,依然要計(jì)算1000萬(wàn)次來(lái)生成迭代列表,這就造成了大量的計(jì)算和內(nèi)存資源。而如果通過生成器重寫該迭代過程的話:
生成器在迭代開始前并不會(huì)計(jì)算出所有需要迭代的值,只有用到時(shí)才會(huì)計(jì)算相應(yīng)的值并返回,因此上述代碼的index將只會(huì)計(jì)算到10即結(jié)束了整個(gè)迭代過程,避免了計(jì)算和內(nèi)存資源的浪費(fèi)。
3. yield語(yǔ)法示例1:DIY一個(gè)range函數(shù)
Python自帶的range函數(shù)可以產(chǎn)生一個(gè)可迭代對(duì)象,常用于for循環(huán)中,在Python 2中range函數(shù)生成的是一個(gè)列表,而在Python 3中range函數(shù)生成的是一個(gè)生成器?,F(xiàn)在讓我們來(lái)通過yield語(yǔ)法DIY一個(gè)自己的range生成器吧!
我們首先構(gòu)造一個(gè)返回給定范圍數(shù)組的函數(shù):
這個(gè)函數(shù)接受兩個(gè)int類型的參數(shù),分別為數(shù)組的開始和結(jié)束,每個(gè)數(shù)之間間隔為1,我們還可以通過增加一個(gè)參數(shù)來(lái)指定兩個(gè)數(shù)之間的間隔,實(shí)現(xiàn)函數(shù)更高的靈活性:
我們先來(lái)運(yùn)行測(cè)試一下這個(gè)range函數(shù):
上述代碼的輸出結(jié)果如下:
2
4
6
輸出結(jié)果符合我們的預(yù)期,現(xiàn)在通過yield語(yǔ)法來(lái)將我們自己DIY的range函數(shù)改造成一個(gè)生成器:
改造起來(lái)也非常簡(jiǎn)單,首先將定義的用來(lái)存儲(chǔ)迭代元素的列表刪除,然后將原來(lái)添加元素到列表中的代碼改造成yield start即可,這樣我們就自己DIY了一個(gè)簡(jiǎn)易的、基于生成器實(shí)現(xiàn)的range函數(shù)。
4. yield語(yǔ)法示例2:讀取文件--《告白氣球》
生成器除了可以用于計(jì)算生成數(shù)字元素外,在IO讀取方面也能起到很大作用,例如在讀取一個(gè)超大文件,或者查詢某個(gè)返回結(jié)果超多的數(shù)據(jù)庫(kù)時(shí),使用通過yield語(yǔ)法構(gòu)造的生成器來(lái)完成讀取操作可以很大程度上降低程序?qū)τ趦?nèi)存的占用。
例如我們有一個(gè)名為my_file.txt的文件,里面存儲(chǔ)了周董的《告白氣球》的歌詞,現(xiàn)在我們可以通過yield語(yǔ)法來(lái)構(gòu)造一個(gè)生成器用于一行一行的讀取每一句歌詞:
這里使用with語(yǔ)法來(lái)讀取文件,這是Python 3推薦的方式。file.readline()函數(shù)每次返回一行內(nèi)容,由于返回的內(nèi)容帶有每行結(jié)尾的換行符,因此通過line.strip(‘ ’)將換行符過濾掉。每次通過yield返回一行內(nèi)容之后,再次通過file.readline()函數(shù)獲取下一行內(nèi)容,直到整個(gè)文件被完全迭代。
讓我們來(lái)運(yùn)行測(cè)試一下這個(gè)按行讀取文件內(nèi)容的生成器:
上述代碼的輸出結(jié)果如下:
塞納河畔 左岸的咖啡
我手一杯 品嘗你的美
留下唇印 的嘴
……
《告白氣球》的歌詞就一行一行的輸出到屏幕上了,由于歌詞行數(shù)過多,因此這邊只復(fù)制出前三行給大家演示結(jié)果。
5. yield語(yǔ)法示例3:斐波那契數(shù)列
斐波那契數(shù)列是一道經(jīng)典的算法題,也是程序員面試時(shí)經(jīng)常會(huì)被問到的一道題。斐波那契數(shù)列的就是一個(gè)形如1, 1, 2, 3, 5, 8, ……的數(shù)列,從第三項(xiàng)開始,每一項(xiàng)都等于前兩項(xiàng)之和。使用Python來(lái)實(shí)現(xiàn)一個(gè)計(jì)算斐波那契數(shù)列的典型函數(shù)如下:
這個(gè)函數(shù)通過一個(gè)名為fib_list的數(shù)組存儲(chǔ)生成的前n個(gè)斐波那契數(shù),最后一次性返回整個(gè)數(shù)組。其中a, b = b, a + b是Python的一個(gè)特色用法,用于快速交換兩個(gè)數(shù),相當(dāng)于:
參考之前DIY的range函數(shù)的寫法,將這個(gè)計(jì)算斐波那契數(shù)列的函數(shù)通過yield語(yǔ)法修改為生成器:
讓我們來(lái)測(cè)試運(yùn)行一下這個(gè)通過yield語(yǔ)法實(shí)現(xiàn)的斐波那契數(shù)列生成器:
對(duì)應(yīng)的輸出結(jié)果為:
1
1
2
3
5
可以看到,從第三項(xiàng)開始的每一項(xiàng)都是前兩項(xiàng)的和,這樣的輸出結(jié)果就是我們要的斐波那契數(shù)列。