Yield快压?
總的來(lái)說(shuō)糙俗,yield
關(guān)鍵字和return
關(guān)鍵字有相似之處,但其工作機(jī)制卻大相徑庭直秆。想要完整地理解yield
的工作機(jī)制濒募,我們首先需要明白什么是generator
。而為了明白什么是generator
圾结,我們需要明白什么是iterable
瑰剃。
本文接下來(lái)將首先給出一種理解含yield
關(guān)鍵字的函數(shù)/代碼的便捷方法。然后討論iterable
和generator
筝野。接著在此基礎(chǔ)上討論yield
的工作機(jī)制晌姚。
理解yield
關(guān)鍵字的葵花寶典
在遇到一段含有yield的函數(shù)的代碼時(shí),使用如下方法我們就可以輕松的讀懂這個(gè)函數(shù)歇竟。
- 在函數(shù)開(kāi)頭插入
result = []
- 將所有的
yield表達(dá)式
替換成result.append(表達(dá)式)
- 在函數(shù)結(jié)尾插入
return result
- 此時(shí)這段代碼已經(jīng)變?yōu)椴缓?code>yield的普通代碼挥唠, 重讀一遍代碼就可以輕松理解該函數(shù)了
- 和原代碼對(duì)比一下,理解yield的意義是什么
通過(guò)這種方法我們可以理解函數(shù)的邏輯是什么焕议,但是實(shí)際上yield
的工作機(jī)制和用list
替換實(shí)現(xiàn)的工作機(jī)制是非常不同的宝磨。在某些情況下,使用yield
關(guān)鍵字從內(nèi)存和運(yùn)行時(shí)間角度看都更加高效。在另外一些情況下唤锉,即使原本的代碼工作良好世囊,使用本方法替換yield
卻可能會(huì)導(dǎo)致死循環(huán)的產(chǎn)生。
iterable 和 iterator
for ele in mylist:
loop body
在Python中窿祥,任何可以使用for ... in ...
模式進(jìn)行循環(huán)的對(duì)象都是iterable
株憾。例如list, string, file
等”诶撸或者說(shuō)号胚,在Python中,實(shí)現(xiàn)了__iter()__
方法的類實(shí)例都是iterable
浸遗。__iter()__
方法需要返回一個(gè)iterator
猫胁。iterator
指的是實(shí)現(xiàn)了next()
方法的對(duì)象。在一個(gè)類中同時(shí)實(shí)現(xiàn)__iter()__
和next()
是合法的跛锌。這種情況下__iter()__
只需要返回self
即可弃秆。在簡(jiǎn)單的應(yīng)用中這樣做沒(méi)有問(wèn)題。但是如果我們想要同時(shí)使用兩個(gè)或兩個(gè)以上的iterator
同時(shí)循環(huán)同一個(gè)對(duì)象髓帽,這種模式就無(wú)法滿足要求了菠赚。
換句話說(shuō),iterable
是任何你能通過(guò)調(diào)用iter()
方法得到iterator
的對(duì)象郑藏。而iterator
是你用來(lái)循環(huán)iterable
對(duì)象的對(duì)象衡查。
for ele in mylist
在Python中的工作模式如下:
- 得到一個(gè)
mylist
的iterator
:調(diào)用iter(mylist)
-->該方法返回一個(gè)實(shí)現(xiàn)了next()
方法(Python3中為__next()__
)的對(duì)象(iterator
)。 - 使用該
iterator
逐個(gè)循環(huán)mylist
中的項(xiàng)目:不斷調(diào)用iterator
中的next()
方法必盖。next()
方法的返回值被賦給ele
拌牲,然后執(zhí)行loop body
中的語(yǔ)句。如果iterator
中所有元素都已被循環(huán)歌粥,則next()
方法拋出StopIteration
異常塌忽,意味著循環(huán)結(jié)束。
事實(shí)上失驶,Python在任何執(zhí)行循環(huán)的時(shí)候都會(huì)執(zhí)行以上兩部——可能是在顯式地for
循環(huán)中土居,也可能是otherlist.extend(mylist)
這樣含有隱式循環(huán)的操作(其中otherlist
是一個(gè)list
)。
所以Python中的iterable
有如下幾種:
- Python中可使用
for ... in ...
循環(huán)的對(duì)象:list, dict, string, files
嬉探。 - 用戶自定義的實(shí)現(xiàn)了
iter()
方法的類實(shí)例擦耀。 -
Generator
。
需要注意的是甲馋,Python中的for
循環(huán)并不關(guān)心它處理的是以上哪一種情況埂奈。只要其處理的是可以通過(guò)next()
方法逐個(gè)調(diào)用元素的iterator
,for
循環(huán)就可以正常工作定躏。例如我們使用for
循環(huán)逐個(gè)訪問(wèn)list
中的元素账磺,dict
中的key
芹敌,file
中的行,等等垮抗。
generator
generator
的功能實(shí)際上同iterator
完全相同氏捞,但其與iterator
的區(qū)別在于generator
中的元素只能被循環(huán)一次。這是因?yàn)?code>generator不會(huì)在內(nèi)存中存儲(chǔ)全部的元素冒版,而是動(dòng)態(tài)地產(chǎn)生下一個(gè)將被循環(huán)的元素液茎。
# 這是一個(gè)iterator
>>> mylist = [x * x for x in range(3)]
>>> for ele in mylist:
... print(ele)
0
1
4
# 這是一個(gè)generator
>>> mylist = (x * x for x in range(3))
>>> for ele in mylist:
... print(ele)
0
1
4
可以看出,只需要將[]
換成()
我們就得到了一個(gè)generator
辞嗡。與iterator
不同的是捆等,對(duì)于generator
,我們只能調(diào)用一次for ele in mylist
续室。因?yàn)?code>generator將先計(jì)算0栋烤,然后刪除這部分內(nèi)容;再計(jì)算1挺狰,然后再刪除這部分內(nèi)容明郭,等等。也就是說(shuō)generator
不會(huì)在內(nèi)存中保存任何之前已經(jīng)循環(huán)過(guò)的元素的值丰泊。
yield
yield
關(guān)鍵字的用法類似于return
薯定,但是區(qū)別在于yield
關(guān)鍵使得函數(shù)返回一個(gè)generator
對(duì)象⊥海看如下示例代碼话侄。
def createGenerator():
yield 1
yield 2
yield 3
myGenerator = createGenerator()
print(myGenerator) #(1)
for ele in myGenerator:
print(ele) #(2)
print(myGenerator.next())
上述代碼的三個(gè)輸出結(jié)果分別為:
<generator object createGenerator at 0x1010b1960> #(1)
1
2
3
#(2)
Traceback (most recent call last):
File "<stdin>", line 12, in <module>
StopIteration
#(3)
從輸出結(jié)果我們可以看出,yield關(guān)鍵字返回了一個(gè)generator
学赛,且generator
只能被循環(huán)一次满葛。
當(dāng)createGenerator()
被調(diào)用的時(shí)候,事實(shí)上其不會(huì)返回yield
語(yǔ)句中的任何值罢屈。該函數(shù)返回的其實(shí)是一個(gè)generator
對(duì)象。并且函數(shù)事實(shí)上也并沒(méi)有退出篇亭,而是處于一種暫停的狀態(tài)缠捌。當(dāng)我們使用for
循環(huán)來(lái)遍歷generator
的時(shí)候,函數(shù)將會(huì)從暫停的狀態(tài)恢復(fù)译蒂,并繼續(xù)執(zhí)行之前最后執(zhí)行的yield
語(yǔ)句后面的代碼曼月。在createGenerator()
中也就是下一行yield
語(yǔ)句,然后將yield
中的值返回給for
循環(huán)柔昼。這個(gè)過(guò)程會(huì)持續(xù)到函數(shù)執(zhí)行至結(jié)尾退出之前(函數(shù)退出哑芹,或者說(shuō)generator
為空意味著函數(shù)持續(xù)運(yùn)行但無(wú)法發(fā)現(xiàn)yield
語(yǔ)句。這可能是由于函數(shù)已經(jīng)執(zhí)行至結(jié)尾捕透,也可能是因?yàn)槲覀兊暮瘮?shù)中包含的if/else
條件無(wú)法被繼續(xù)滿足)聪姿。此時(shí)generator
會(huì)拋出StopIteration
異常碴萧,for
循環(huán)結(jié)束。需要注意的是末购,在for
循環(huán)第一次調(diào)用generator
時(shí)破喻,函數(shù)將會(huì)從開(kāi)頭執(zhí)行至第一個(gè)yield
并返回該語(yǔ)句中的值。
相反盟榴,如果我們用return
代替createGenerator()
中的yield
曹质,則函數(shù)中只有第一行會(huì)被執(zhí)行,函數(shù)返回1
后就會(huì)結(jié)束擎场。
從某種程度上來(lái)說(shuō)羽德,generator
的作用類似于一個(gè)適配器。一方面其next()
方法使得for
循環(huán)能夠逐個(gè)取得下一個(gè)值迅办。另一方面宅静,它將函數(shù)執(zhí)行至恰好取得下一個(gè)值的部分,然后將函數(shù)重新置為暫停狀態(tài)礼饱。
為什么使用generator?
通常情況下坏为,我們可以寫出不使用generator
但是實(shí)現(xiàn)完全相同的邏輯的代碼。方法之一就是使用前面替換yield
關(guān)鍵字的list
方法镊绪。但這種方法并不是永遠(yuǎn)都有效匀伏。比如當(dāng)我們有無(wú)限長(zhǎng)的循環(huán)的時(shí)候;并且如果list
非常大的話蝴韭,這種方法對(duì)內(nèi)存的利用效率非常低够颠。
另一種方法是實(shí)現(xiàn)另一個(gè)iterable
類并利用這個(gè)類中的實(shí)例成員記錄狀態(tài)。利用這個(gè)類中實(shí)現(xiàn)的next()
方法(Python3中的__next()__
)來(lái)實(shí)現(xiàn)逐個(gè)循環(huán)的操作榄鉴。根據(jù)邏輯設(shè)計(jì)的需要履磨,next()
中的代碼塊可能會(huì)非常復(fù)雜并且容易產(chǎn)生bug。這種情況下庆尘,使用generator
也可以提供干凈利落的解決方法剃诅。
控制generator元素的耗盡
>>> class Bank(): # let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
在某些需要控制對(duì)某個(gè)資源的獲取的時(shí)候,這個(gè)方法可能會(huì)有用驶忌。