Python中關(guān)鍵字yield有什么作用?

yield有什么用?

例如下面這段代碼:

def node._get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

下面是調(diào)用它:

result, candidates = list(), [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

當(dāng)_get_child_candidates方法被調(diào)用的時候發(fā)生了什么?是返回一個列表?還是一個元祖?它還能第二次調(diào)用嗎?后面的調(diào)用什么時候結(jié)束?


為了理解yield有什么用,首先得理解generators,而理解generators前還要理解iterables

Iterables

當(dāng)你創(chuàng)建了一個列表,你可以一個一個的讀取它的每一項(xiàng),這叫做iteration:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

Mylist是可迭代的.當(dāng)你用列表推導(dǎo)式的時候,你就創(chuàng)建了一個列表,而這個列表也是可迭代的:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

所有你可以用在for...in...語句中的都是可迭代的:比如lists,strings,files...因?yàn)檫@些可迭代的對象你可以隨意的讀取所以非常方便易用,但是你必須把它們的值放到內(nèi)存里,當(dāng)它們有很多值時就會消耗太多的內(nèi)存.

Generators

生成器也是迭代器的一種,但是你只能迭代它們一次.原因很簡單,因?yàn)樗鼈儾皇侨看嬖趦?nèi)存里,它們只在要調(diào)用的時候在內(nèi)存里生成:

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

生成器和迭代器的區(qū)別就是用()代替[],還有你不能用for i in mygenerator第二次調(diào)用生成器:首先計(jì)算0,然后會在內(nèi)存里丟掉0去計(jì)算1,直到計(jì)算完4.

Yield

Yield的用法和關(guān)鍵字return差不多,下面的函數(shù)將會返回一個生成器:

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # 創(chuàng)建生成器
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

在這里這個例子好像沒什么用,不過當(dāng)你的函數(shù)要返回一個非常大的集合并且你希望只讀一次的話,那么它就非常的方便了.

要理解Yield你必須先理解當(dāng)你調(diào)用函數(shù)的時候,函數(shù)里的代碼并沒有運(yùn)行.函數(shù)僅僅返回生成器對象,這就是它最微妙的地方:-)

然后呢,每當(dāng)for語句迭代生成器的時候你的代碼才會運(yùn)轉(zhuǎn).

現(xiàn)在,到了最難的部分:

當(dāng)for語句第一次調(diào)用函數(shù)里返回的生成器對象,函數(shù)里的代碼就開始運(yùn)作,直到碰到yield,然后會返回本次循環(huán)的第一個返回值.所以下一次調(diào)用也將運(yùn)行一次循環(huán)然后返回下一個值,直到?jīng)]有值可以返回.

一旦函數(shù)運(yùn)行并沒有碰到yeild語句就認(rèn)為生成器已經(jīng)為空了.原因有可能是循環(huán)結(jié)束或者沒有滿足if/else之類的.

對于你的代碼的解釋

生成器:


# 這里你創(chuàng)建node方法的對象將會返回一個生成器
def node._get_child_candidates(self, distance, min_dist, max_dist):

  # 這里的代碼你每次使用生成器對象的時候?qū){(diào)用

  if self._leftchild and distance - max_dist < self._median:
      yield self._leftchild

  if self._rightchild and distance + max_dist >= self._median:
      yield self._rightchild

  # 如果代碼運(yùn)行到這里,生成器就被認(rèn)為變成了空的

調(diào)用:

# 創(chuàng)建空列表和一個當(dāng)前對象索引的列表
result, candidates = list(), [self]

# 在candidates上進(jìn)行循環(huán)(在開始只保含一個元素)
while candidates:

    # 獲得最后一個condidate然后從列表里刪除
    node = candidates.pop()

    # 獲取obj和candidate的distance
    distance = node._get_dist(obj)

    # 如果distance何時將會填入result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

這段代碼有幾個有意思的地方:

一般的時候我們會在循環(huán)迭代一個列表的同時在列表中添加元素:-)盡管在有限循環(huán)里結(jié)束多少有一些危險(xiǎn),但也不失為一個簡單的方法去遍歷嵌套的數(shù)據(jù).在這里candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))將遍歷生成器的每一個值,但是while循環(huán)中的condidates將不再保存已經(jīng)遍歷過的生成器對象馆纳,也就是說添加進(jìn)condidates的生成器對象只會遍歷一遍。

extend()是一個列表對象的方法,它可以把一個迭代對象添加進(jìn)列表.

我們經(jīng)常這么用:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

但是在你給的代碼里得到的是生成器,這樣做的好處:

你不需要讀這個值兩次

你能得到許多孩子節(jié)點(diǎn)但是你不希望他們?nèi)看嫒雰?nèi)存.

這種方法之所以能很好的運(yùn)行是因?yàn)镻ython不關(guān)心方法的參數(shù)是不是一個列表.它只希望接受一個迭代器,所以不管是strings,lists,tuples或者generators都可以!這種方法叫做duck typing,這也是Python看起來特別cool的原因之一.但是這又是另外一個傳說了,另一個問題~~

好了,看到這里可以打住了,下面讓我們看看生成器的高級用法:

控制迭代器的窮盡

>>> class Bank(): # 讓我們建個銀行,生產(chǎn)許多ATM
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # 當(dāng)一切就緒了你想要多少ATM就給你多少
>>> 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 # cao,經(jīng)濟(jì)危機(jī)來了沒有錢了!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # 對于其他ATM,它還是True
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # 麻煩的是,盡管危機(jī)過去了,ATM還是空的
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # 只能重新新建一個bank了
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

它對于一些不斷變化的值很有用,像控制你資源的訪問.

Itertools,你的好基友

itertools模塊包含了一些特殊的函數(shù)可以操作可迭代對象.有沒有想過復(fù)制一個生成器?鏈接兩個生成器?把嵌套列表里的值組織成一個列表?Map/Zip還不用創(chuàng)建另一個列表?

來吧import itertools

來一個例子?讓我們看看4匹馬比賽有多少個排名結(jié)果:

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

理解迭代的內(nèi)部機(jī)制

迭代是可迭代對象(對應(yīng)__iter__()方法)和迭代器(對應(yīng)__next__()方法)的一個過程.可迭代對象就是任何你可以迭代的對象(廢話啊).迭代器就是可以讓你迭代可迭代對象的對象(有點(diǎn)繞口,意思就是這個意思)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昂勒,一起剝皮案震驚了整個濱河市比庄,隨后出現(xiàn)的幾起案子伴澄,更是在濱河造成了極大的恐慌外邓,老刑警劉巖益咬,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異婉刀,居然都是意外死亡吟温,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門突颊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鲁豪,“玉大人潘悼,你說我怎么就攤上這事∨老穑” “怎么了挥等?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長堤尾。 經(jīng)常有香客問我,道長迁客,這世上最難降的妖魔是什么郭宝? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮掷漱,結(jié)果婚禮上粘室,老公的妹妹穿的比我還像新娘。我一直安慰自己卜范,他們只是感情好衔统,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著海雪,像睡著了一般锦爵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奥裸,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天险掀,我揣著相機(jī)與錄音,去河邊找鬼湾宙。 笑死樟氢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的侠鳄。 我是一名探鬼主播埠啃,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼伟恶!你這毒婦竟也來了碴开?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤知押,失蹤者是張志新(化名)和其女友劉穎叹螟,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體台盯,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡罢绽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了静盅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片良价。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡寝殴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出明垢,到底是詐尸還是另有隱情蚣常,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布痊银,位于F島的核電站抵蚊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏溯革。R本人自食惡果不足惜贞绳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望致稀。 院中可真熱鬧冈闭,春花似錦、人聲如沸抖单。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矛绘。三九已至耍休,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間货矮,已是汗流浹背羹应。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留次屠,地道東北人园匹。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像劫灶,于是被迫代替她去往敵國和親裸违。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

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