(譯)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