Python關(guān)鍵字yield的解釋(stackoverflow)

(譯)Python關(guān)鍵字yield的解釋(stackoverflow)

譯者: hit9

原文: http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained

譯者注: 這是 stackoverflow 上一個(gè)很熱的帖子俗壹,這里是投票最高的一個(gè)答案

提問者的問題

Python 關(guān)鍵字 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)調(diào)用 _get_child_candidates 的時(shí)候發(fā)生了什么扇单?返回了一個(gè)鏈表?返回了一個(gè)元素憔涉?被重復(fù)調(diào)用了么骏融? 什么時(shí)候這個(gè)調(diào)用結(jié)束呢?

回答部分

為了理解什么是 yield懂版,你必須理解什么是生成器鹃栽。在理解生成器之前,讓我們先走近迭代躯畴。

可迭代對(duì)象

當(dāng)你建立了一個(gè)列表民鼓,你可以逐項(xiàng)地讀取這個(gè)列表,這叫做一個(gè)可迭代對(duì)象:

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

mylist 是一個(gè)可迭代的對(duì)象蓬抄。當(dāng)你使用一個(gè)列表生成式來建立一個(gè)列表的時(shí)候丰嘉,就建立了一個(gè)可迭代的對(duì)象:

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

所有你可以使用 for .. in ..語法的叫做一個(gè)迭代器:鏈表,字符串嚷缭,文件……你經(jīng)常使用它們是因?yàn)槟憧梢匀缒闼傅淖x取其中的元素饮亏,但是你把所有的值都存儲(chǔ)到了內(nèi)存中,如果你有大量數(shù)據(jù)的話這個(gè)方式并不是你想要的阅爽。

生成器(generator)

生成器是可以迭代的路幸,但是你只可以讀取它一次 ,因?yàn)樗⒉话阉械闹捣旁趦?nèi)存中付翁,它是實(shí)時(shí)地生成數(shù)據(jù):

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

看起來除了把 [] 換成 () 外沒什么不同简肴。但是,你不可以再次使用 for i in mygenerator百侧, 因?yàn)樯善髦荒鼙坏淮危合扔?jì)算出0砰识,然后繼續(xù)計(jì)算1,然后計(jì)算4佣渴,一個(gè)跟一個(gè)的…

yield關(guān)鍵字

yield 是一個(gè)類似 return 的關(guān)鍵字辫狼,只是這個(gè)函數(shù)返回的是個(gè)生成器

>>> def createGenerator() :
...    mylist = range(3)
...    for i in mylist :
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

這個(gè)例子沒什么用途观话,但是它讓你知道予借,這個(gè)函數(shù)會(huì)返回一大批你只需要讀一次的值。

為了精通 yield ,你必須要理解:當(dāng)你調(diào)用這個(gè)函數(shù)的時(shí)候,函數(shù)內(nèi)部的代碼并不立馬執(zhí)行 灵迫,這個(gè)函數(shù)只是返回一個(gè)生成器對(duì)象秦叛,這有點(diǎn)蹊蹺不是嗎。

那么瀑粥,函數(shù)內(nèi)的代碼什么時(shí)候執(zhí)行呢挣跋?當(dāng)你使用for進(jìn)行迭代的時(shí)候.

現(xiàn)在到了關(guān)鍵點(diǎn)了!

第一次迭代中你的函數(shù)會(huì)執(zhí)行狞换,從開始到達(dá) yield 關(guān)鍵字避咆,然后返回 yield 后的值作為第一次迭代的返回值. 然后,每次執(zhí)行這個(gè)函數(shù)都會(huì)繼續(xù)執(zhí)行你在函數(shù)內(nèi)部定義的那個(gè)循環(huán)的下一次修噪,再返回那個(gè)值查库,直到?jīng)]有可以返回的。

如果生成器內(nèi)部沒有定義 yield 關(guān)鍵字黄琼,那么這個(gè)生成器被認(rèn)為成空的樊销。這種情況可能因?yàn)槭茄h(huán)進(jìn)行沒了,或者是沒有滿足 if/else 條件脏款。

回到你的代碼

(譯者注:這是回答者對(duì)問題的具體解釋)

生成器:

# Here you create the method of the node object that will return the generator
def node._get_child_candidates(self, distance, min_dist, max_dist):

  # Here is the code that will be called each time you use the generator object :

  # If there is still a child of the node object on its left
  # AND if distance is ok, return the next child
  if self._leftchild and distance - max_dist < self._median:
            yield self._leftchild

  # If there is still a child of the node object on its right
  # AND if distance is ok, return the next child
  if self._rightchild and distance + max_dist >= self._median:
                yield self._rightchild

  # If the function arrives here, the generator will be considered empty
  # there is no more than two values : the left and the right children

調(diào)用者:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

這個(gè)代碼包含了幾個(gè)小部分:

我們對(duì)一個(gè)鏈表進(jìn)行迭代围苫,但是迭代中鏈表還在不斷的擴(kuò)展。它是一個(gè)迭代這些嵌套的數(shù)據(jù)的簡潔方式撤师,即使這樣有點(diǎn)危險(xiǎn)剂府,因?yàn)榭赡軐?dǎo)致無限迭代。 candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))窮盡了生成器的所有值剃盾,但 while 不斷地在產(chǎn)生新的生成器腺占,它們會(huì)產(chǎn)生和上一次不一樣的值,既然沒有作用到同一個(gè)節(jié)點(diǎn)上.
extend() 是一個(gè)迭代器方法痒谴,作用于迭代器湾笛,并把參數(shù)追加到迭代器的后面。
通常我們傳給它一個(gè)鏈表參數(shù):

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

但是在你的代碼中的是一個(gè)生成器闰歪,這是不錯(cuò)的,因?yàn)椋?/p>

你不必讀兩次所有的值
你可以有很多子對(duì)象蓖墅,但不必叫他們都存儲(chǔ)在內(nèi)存里面库倘。
并且這很奏效,因?yàn)?Python 不關(guān)心一個(gè)方法的參數(shù)是不是個(gè)鏈表论矾。Python 只希望它是個(gè)可以迭代的教翩,所以這個(gè)參數(shù)可以是鏈表,元組贪壳,字符串饱亿,生成器... 這叫做 duck typing,這也是為何 Python 如此棒的原因之一,但這已經(jīng)是另外一個(gè)問題了...

你可以在這里停下彪笼,來看看生成器的一些高級(jí)用法:

控制生成器的窮盡

>>> 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ì)于控制一些資源的訪問來說這很有用钻注。

Itertools,你最好的朋友

itertools 包含了很多特殊的迭代方法。是不是曾想過復(fù)制一個(gè)迭代器?串聯(lián)兩個(gè)迭代器配猫?把嵌套的鏈表分組幅恋?不用創(chuàng)造一個(gè)新的鏈表的 zip/map?

只要 import itertools

需要個(gè)例子?讓我們看看比賽中4匹馬可能到達(dá)終點(diǎn)的先后順序的可能情況:

>>> 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ī)理

迭代是一個(gè)實(shí)現(xiàn)可迭代對(duì)象(實(shí)現(xiàn)的是 __iter__() 方法)和迭代器(實(shí)現(xiàn)的是 __next__() 方法)的過程泵肄±唬可迭代對(duì)象是你可以從其獲取到一個(gè)迭代器的任一對(duì)象。迭代器是那些允許你迭代可迭代對(duì)象的對(duì)象腐巢。

via: http://pyzh.readthedocs.org/en/latest/the-python-yield-keyword-explained.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末品追,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子冯丙,更是在濱河造成了極大的恐慌肉瓦,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件银还,死亡現(xiàn)場離奇詭異风宁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蛹疯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門戒财,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捺弦,你說我怎么就攤上這事饮寞。” “怎么了列吼?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵幽崩,是天一觀的道長。 經(jīng)常有香客問我寞钥,道長慌申,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任理郑,我火速辦了婚禮蹄溉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘您炉。我一直安慰自己柒爵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布赚爵。 她就那樣靜靜地躺著棉胀,像睡著了一般法瑟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唁奢,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天霎挟,我揣著相機(jī)與錄音,去河邊找鬼驮瞧。 笑死氓扛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的论笔。 我是一名探鬼主播采郎,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼狂魔!你這毒婦竟也來了蒜埋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤最楷,失蹤者是張志新(化名)和其女友劉穎整份,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體籽孙,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烈评,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了犯建。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讲冠。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖适瓦,靈堂內(nèi)的尸體忽然破棺而出竿开,到底是詐尸還是另有隱情,我是刑警寧澤玻熙,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布否彩,位于F島的核電站,受9級(jí)特大地震影響嗦随,放射性物質(zhì)發(fā)生泄漏列荔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一枚尼、第九天 我趴在偏房一處隱蔽的房頂上張望肌毅。 院中可真熱鬧,春花似錦姑原、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春唤殴,著一層夾襖步出監(jiān)牢的瞬間般婆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國打工朵逝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蔚袍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓配名,卻偏偏與公主長得像啤咽,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渠脉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

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