新聞中心
一、 生成器(generator)概念

成都創(chuàng)新互聯(lián)專注于雙峰網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗。 熱誠為您提供雙峰營銷型網(wǎng)站建設(shè),雙峰網(wǎng)站制作、雙峰網(wǎng)頁設(shè)計、雙峰網(wǎng)站官網(wǎng)定制、微信小程序定制開發(fā)服務(wù),打造雙峰網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供雙峰網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
生成器是一個特殊的迭代器,它保存的是算法,每次調(diào)用next()或send()就計算出下一個元素的值,直到計算出最后一個元素,沒有更多的元素時,拋出StopIteration。生成器有兩種類型,一種是生成器表達(dá)式(又稱為生成器推導(dǎo)),一種是生成器函數(shù)。
二、 生成器表達(dá)式
生成器表達(dá)式是通過一個Python表達(dá)式語句去計算一系列數(shù)據(jù),但生成器定義的時候數(shù)據(jù)并沒有生成,而是返回一個對象,這個對象只有在需要的時候才根據(jù)表達(dá)式計算當(dāng)前需要返回的數(shù)據(jù):
生成器表達(dá)式來源于迭代和列表解析(列表解析后面章節(jié)介紹)的組合,生成器和列表解析類似,但是它使用小括號而不是中括號。生成器返回按需產(chǎn)生結(jié)果的一個對象,而不是一次構(gòu)建一個結(jié)果列表;
生成器表達(dá)式的語法如下:
(expr for iter_var in iterable) (expr for iter_var in iterable if cond_expr)
其中:
expr為計算 生成器元素值的表達(dá)式
for iter_var in iterable iter_var:表示針對在可迭代對象iter_var中的每個元素進(jìn)行表達(dá)式運(yùn)算
if cond_exp:表示可迭代對象中的元素需要滿足指定條件才會參與表達(dá)式運(yùn)算
說明
直接在一對既有的小括號內(nèi)(如在函數(shù)調(diào)用中)使用生成器表達(dá)式時,無需再添加一對小括號。例如:sum(i ** 2 for i in range(10));
生成器表達(dá)式與列表解析的語法非常象,由于涉及部分相關(guān)的函數(shù),在列表解析相關(guān)的章節(jié)老猿再回頭介紹一下生成器表達(dá)式有關(guān)的內(nèi)容。
三、 生成器函數(shù)
生成器函數(shù)是一種語句中包含yield關(guān)鍵詞的特殊的函數(shù),它本身是一個迭代器,外部需要訪問該迭代器數(shù)據(jù)的代碼通過調(diào)用next函數(shù)(或迭代器的__next__方法)或send方法,觸發(fā)函數(shù)執(zhí)行計算并通過yield返回一個計算結(jié)果數(shù)據(jù),返回數(shù)據(jù)后該函數(shù)立即停止執(zhí)行,函數(shù)狀態(tài)會保存在本地變量中,直到外部下次調(diào)用再激活,從上次停止執(zhí)行部分開始執(zhí)行。
1、 關(guān)于生成器函數(shù)與調(diào)用方的執(zhí)行過程解析
生成器函數(shù)定義示意代碼(非可執(zhí)行代碼)如下:
def fun():
初始化
循環(huán):
計算得到k
nRet=yield k
其他循環(huán)代碼
上面代碼示意表示:生成器函數(shù)運(yùn)行時計算得到結(jié)果k通過yield返回數(shù)據(jù)k給調(diào)用方,返回k給調(diào)用方之后,生成器函數(shù)停止執(zhí)行,yield的調(diào)用執(zhí)行結(jié)果并沒有返回給生成器函數(shù), nRet的賦值也并沒有執(zhí)行,等待下次調(diào)用后,再返回yield本身的執(zhí)行結(jié)果,并繼續(xù)后續(xù)循環(huán)代碼,直到再次執(zhí)行yield。
老猿通過驗證理解有幾個細(xì)節(jié)在此說明一下:
a) yield函數(shù)的執(zhí)行是一條語句,但實際執(zhí)行時該語句被分解成兩部分,第一部分是將計算結(jié)果k返回給send或next調(diào)用處(下稱觸發(fā)方),保存當(dāng)前環(huán)境,暫停執(zhí)行,另一部分就是恢復(fù)當(dāng)前環(huán)境,返回yield本身的執(zhí)行結(jié)果給生成器函數(shù)的調(diào)用處,并繼續(xù)往下執(zhí)行后續(xù)循環(huán)。每次調(diào)用yield時,除了第一次是從第一部分執(zhí)行,后續(xù)都是從第二部分開始執(zhí)行。
b) yield返回值(nRet記下來的值)在觸發(fā)方為next(含__next__方法,下同)時,為None,如果觸發(fā)方是send,則該值為send方法參數(shù)中的發(fā)送值;
c) 生成器函數(shù)在調(diào)用時只是生成一個生成器實例,并沒有真正執(zhí)行,真正執(zhí)行只有第一次通過next觸發(fā)時才會進(jìn)入函數(shù)執(zhí)行,注意第一次觸發(fā)不能是send方式觸發(fā)。
2) 調(diào)用生成器代碼示意
def main():
初始化
f= fun() next(f)
循環(huán):
其他循環(huán)代碼
nRet=send(x)
其他循環(huán)代碼
上面代碼示意表示:調(diào)用方執(zhí)行自身初始化,然后進(jìn)行生成器函數(shù)的初始化,然后執(zhí)行循環(huán)迭代訪問生成器函數(shù)的數(shù)據(jù)。
同樣有幾個細(xì)節(jié)老猿在此說明一下:
a) f= fun(),這個語句不會進(jìn)入函數(shù)執(zhí)行,只是生成一個生成器實例f
b) 第一個next調(diào)用只有循環(huán)代碼中使用send觸發(fā)時才需要,如果循環(huán)中用next則無需先執(zhí)行一次send;
c) 第一個next執(zhí)行時會觸發(fā)調(diào)用生成器函數(shù),從生成器第一行代碼開始執(zhí)行;后續(xù)的next或send執(zhí)行,不再執(zhí)行生成器函數(shù)的初始化部分,只是從yield的第二部分開始執(zhí)行,第二部分執(zhí)行時應(yīng)該在生成器函數(shù)的循環(huán)迭代代碼內(nèi),因此此后執(zhí)行還是在生成器函數(shù)的循環(huán)代碼內(nèi)循環(huán),直到遇到y(tǒng)ield語句,執(zhí)行完yield語句的第一部分邏輯掛起函數(shù)等待再次出發(fā);
d) nRet記錄的返回值就是生成器函數(shù)yield后面返回給觸發(fā)方的數(shù)據(jù)。
2、 下面是一個老猿編寫的模擬存快遞包裹的生成器函數(shù)及其調(diào)用代碼,每執(zhí)行一次存包裹的函數(shù)就掛起,主程序等待確認(rèn)是否繼續(xù)循環(huán),如果不繼續(xù)則退出,代碼如下:
import random
def PutPackage():
print(‘PutPackage start…’)
nRet = 123
while True:
if nRet<1 : break
print(‘PutPackage:Before Yield…’)
nRet = yield ’ PutPackage’+str(nRet) #返回字符串PutPackage+上次循環(huán)yield的返回值
print(‘PutPackage:After Yield, nRet=’,nRet)
if not nRet: continue
def mainf():
print(‘mainf start call PutPackage …’)
vPutPackage=PutPackage() #只是返回生成器generator對象
bBreak = False
print(‘mainf start call next …’)
nRet=next(vPutPackage) #生成器初始化
print(‘mainf end call next,nRet=’,nRet)
while True:
if bBreak:
try: #為什么要捕獲異常?
vPutPackage.send(-1) #觸發(fā)—1給生成器函數(shù)提示函數(shù)退出
except StopIteration:pass
break
print(‘mainf loop start call send …’)
nRet=vPutPackage.send(random.randint(10000,99999)) #產(chǎn)生一個隨機(jī)包裹編號觸發(fā)給生成器函數(shù)
print(‘mainf loop after call send ,nRet=’,nRet)
sConfirm=input(“是否準(zhǔn)備結(jié)束存件取件循環(huán)(Y或y是,否則繼續(xù)循環(huán)):”)
if sConfirm.strip().upper()==‘Y’:
bBreak=True
print("\n")
mainf()執(zhí)行結(jié)果如下,大家對照前面的執(zhí)行過程解析理解一下:
mainf start call PutPackage … mainf start call next … PutPackage start… PutPackage:Before Yield… mainf end call next,nRet= PutPackage123 mainf loop start call send … PutPackage:After Yield, nRet= 66468 PutPackage:Before Yield… mainf loop after call send ,nRet= PutPackage66468
是否準(zhǔn)備結(jié)束存件取件循環(huán)(Y或y是,否則繼續(xù)循環(huán)):n
mainf loop start call send … PutPackage:After Yield, nRet= 22204 PutPackage:Before Yield… mainf loop after call send ,nRet= PutPackage22204
是否準(zhǔn)備結(jié)束存件取件循環(huán)(Y或y是,否則繼續(xù)循環(huán)):y
PutPackage:After Yield, nRet= -1
上述代碼中為什么要捕獲異常?這是因為最后一個send(-1)時,是從yield第二部分執(zhí)行,執(zhí)行到循環(huán)“if nRet<1 : break”語句就會終止循環(huán),不會再通過yield向觸發(fā)方返回值,此時send執(zhí)行就會出現(xiàn)迭代結(jié)束的異常。
3、 生成器函數(shù)的其他說明
Python使用生成器對延遲操作提供了支持。所謂延遲操作,是指在需要的時候才產(chǎn)生結(jié)果,而不是立即產(chǎn)生結(jié)果。這有利于節(jié)省內(nèi)存,特別是生成器進(jìn)行科學(xué)計算時很有用;
生成器就是迭代器,除了next方法外,也可以通過for循環(huán)來遍歷出生成器中的內(nèi)容;
生成器除了前面介紹的__next__、send方法外,還有throw、close方法:
a) throw(type[, value[, traceback]]):該方法在生成器暫停的位置引發(fā) type 類型的異常,并返回該生成器函數(shù)所產(chǎn)生的下一個值。 如果生成器沒有產(chǎn)生下一個值就退出,則將引發(fā) StopIteration 異常。 如果生成器函數(shù)沒有捕獲傳入的異常,或引發(fā)了另一個異常,則該異常會被傳播給調(diào)用者。該方法可以解決上面案例捕獲異常的處理
b) close():在生成器函數(shù)暫停的位置引發(fā) GeneratorExit。 如果之后生成器函數(shù)正常退出、關(guān)閉或引發(fā) GeneratorExit(由于未捕獲該異常) 則關(guān)閉并返回其調(diào)用者。 如果生成器產(chǎn)生了一個值,關(guān)閉會引發(fā) RuntimeError。 如果生成器引發(fā)任何其他異常,它會被傳播給調(diào)用者。 如果生成器已經(jīng)由于異?;蛘M顺鰟t close() 不會做任何事。通過觸發(fā)方調(diào)用close方法可以直接關(guān)閉生成器,而不需要象上面案例一樣在生成器函數(shù)內(nèi)判斷send發(fā)送的數(shù)據(jù)來進(jìn)行退出。
文章名稱:創(chuàng)新互聯(lián)Python教程:神秘而強(qiáng)大的Python生成器精講
網(wǎng)站網(wǎng)址:http://m.5511xx.com/article/dheoehh.html


咨詢
建站咨詢
