Python中的yield關(guān)鍵字

Yield快压?

總的來(lái)說(shuō)糙俗,yield關(guān)鍵字和return關(guān)鍵字有相似之處,但其工作機(jī)制卻大相徑庭直秆。想要完整地理解yield的工作機(jī)制濒募,我們首先需要明白什么是generator。而為了明白什么是generator圾结,我們需要明白什么是iterable瑰剃。

本文接下來(lái)將首先給出一種理解含yield關(guān)鍵字的函數(shù)/代碼的便捷方法。然后討論iterablegenerator筝野。接著在此基礎(chǔ)上討論yield的工作機(jī)制晌姚。

理解yield關(guān)鍵字的葵花寶典

在遇到一段含有yield的函數(shù)的代碼時(shí),使用如下方法我們就可以輕松的讀懂這個(gè)函數(shù)歇竟。

  1. 在函數(shù)開(kāi)頭插入result = []
  2. 將所有的yield表達(dá)式替換成result.append(表達(dá)式)
  3. 在函數(shù)結(jié)尾插入return result
  4. 此時(shí)這段代碼已經(jīng)變?yōu)椴缓?code>yield的普通代碼挥唠, 重讀一遍代碼就可以輕松理解該函數(shù)了
  5. 和原代碼對(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)生。

iterableiterator

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中的工作模式如下:

  1. 得到一個(gè)mylistiterator:調(diào)用iter(mylist)-->該方法返回一個(gè)實(shí)現(xiàn)了next()方法(Python3中為__next()__)的對(duì)象(iterator)。
  2. 使用該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有如下幾種:

  1. Python中可使用for ... in ...循環(huán)的對(duì)象:list, dict, string, files嬉探。
  2. 用戶自定義的實(shí)現(xiàn)了iter()方法的類實(shí)例擦耀。
  3. Generator

需要注意的是甲馋,Python中的for循環(huán)并不關(guān)心它處理的是以上哪一種情況埂奈。只要其處理的是可以通過(guò)next()方法逐個(gè)調(diào)用元素的iteratorfor循環(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ì)有用驶忌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末矛辕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子付魔,更是在濱河造成了極大的恐慌聊品,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件几苍,死亡現(xiàn)場(chǎng)離奇詭異翻屈,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)妻坝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門伸眶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惊窖,“玉大人,你說(shuō)我怎么就攤上這事赚抡∨揽樱” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵涂臣,是天一觀的道長(zhǎng)盾计。 經(jīng)常有香客問(wèn)我,道長(zhǎng)赁遗,這世上最難降的妖魔是什么署辉? 我笑而不...
    開(kāi)封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮岩四,結(jié)果婚禮上哭尝,老公的妹妹穿的比我還像新娘。我一直安慰自己剖煌,他們只是感情好材鹦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著耕姊,像睡著了一般桶唐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茉兰,一...
    開(kāi)封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天尤泽,我揣著相機(jī)與錄音,去河邊找鬼规脸。 笑死坯约,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的莫鸭。 我是一名探鬼主播闹丐,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼被因!你這毒婦竟也來(lái)了妇智?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤氏身,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后惑畴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蛋欣,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年如贷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了陷虎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片到踏。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖尚猿,靈堂內(nèi)的尸體忽然破棺而出窝稿,到底是詐尸還是另有隱情,我是刑警寧澤凿掂,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布伴榔,位于F島的核電站,受9級(jí)特大地震影響庄萎,放射性物質(zhì)發(fā)生泄漏踪少。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一糠涛、第九天 我趴在偏房一處隱蔽的房頂上張望援奢。 院中可真熱鬧,春花似錦忍捡、人聲如沸集漾。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)具篇。三九已至,卻和暖如春脓规,著一層夾襖步出監(jiān)牢的瞬間栽连,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工侨舆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秒紧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓挨下,卻偏偏與公主長(zhǎng)得像熔恢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子臭笆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容