這是stackoverflow上一個關於python中yield用法的帖子,這裏翻譯自投票最高的一個回答,原文連接 herenode
Python中yield
關鍵字的用途是什麼?它有什麼做用?
例如,我試圖理解如下代碼 ¹:python
def _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
這是調用者(caller):ide
result, candidates = [], [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
當調用方法_get_child_candidates
時會發生什麼?返回了一個列表(list)?仍是返回了一個元素?而後被重複調用了嗎?調用什麼時候結束?函數
¹ :代碼來自 Jochen Schulz (jrschulz), who made a great Python library for metric spaces. 這是完整源代碼的連接:Module mspace.oop
要想理解yield
的做用,你必須瞭解什麼是生成器(generators),在這以前,咱們先來看可迭代對象(iterables)。post
當你建立了一個列表,你能夠遍歷這個列表讀取它的每個元素,逐個讀取列表元素稱爲迭代(iteration)。ui
>>> mylist = [1, 2, 3] >>> for i in mylist: ... print(i) 1 2 3
mylist
就是一個可迭代對象(iterable)。當你使用列表生成式(list comprehension)建立一個列表(list),即建立了一個可迭代對象。this
>>> mylist = [x*x for x in range(3)] >>> for i in mylist: ... print(i) 0 1 4
可使用for... in...
的全部對象都是可迭代對象:列表(lists)、字符串、文件...
這些可迭代對象使用很方便,由於你能夠根據須要如你所願的讀取其中的元素。可是,當你有大量數據時把全部值都存儲在內存中,這樣每每不是你想要的( but you store all the values in memory and this is not always what you want when you have a lot of values.)。spa
生成器是迭代器(iterators),可是只能迭代一次,生成器不會將全部值存儲在內存中,而是實時的生成這些值:翻譯
>>> mygenerator = (x*x for x in range(3)) >>> for i in mygenerator: ... print(i) 0 1 4
看上去除了用()
替換了原來的[]
外,它們沒什麼不一樣。可是,你不能夠再次使用for i in mygenerator
,由於生成器只能被迭代一次:計算出0,而後並不保存結果和狀態繼續計算出1,最後計算出4,逐一輩子成。
yield
是一個相似 return
的關鍵字,不一樣的是這個函數將返回一個生成器。
>>> 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
這個例子沒有什麼實際做用。可是當你知道你的函數將返回大量你只須要讀取一次的值時,使用生成器是一個有效的作法。
要掌握 yeild
,你必需要知道當你調用這個函數時,你在函數體中編寫的代碼並無立馬執行。
該函數僅僅返回一個生成器對象,這有點棘手 :-)
而後,你的代碼將從for
循環每次使用生成器中止的位置繼續執行。
如今到了關鍵部分:
for
第一次調用從函數建立的生成器對象,函數將從頭開始執行直到遇到yeild
,而後返回yield
後的值做爲第一次迭代的返回值。接下來每次調用都會再次執行你在函數中定義的循環,並返回(return)下一個值,直到沒有值能夠返回(return)。
當循環結束,或者不知足if/else
條件,致使函數運行但不會執行(not hit)yeild
,此時生成器被認爲是空的。
生成器 (Generator):
# Here you create the method of the node object that will return the generator def _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
調用者 (Caller):
# 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
這段代碼包含幾個高明的部分:
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
窮盡了生成器產生的全部值,但while
不斷的建立新的生成器對象加入到列表,由於每一個對象做用在不一樣節點上,因此每一個生成器都將生成不一樣的值。extend()
是一個列表(list)對象的方法,做用於可迭代對象(iterable),並將其值添加到列表裏。一般,一般咱們將列表做爲參數傳遞給它:
>>> a = [1, 2] >>> b = [3, 4] >>> a.extend(b) >>> print(a) [1, 2, 3, 4]
可是在你的代碼裏它接收到的是一個生成器(generator),這很好,由於:
它頗有效,由於Python不關心一個方法的參數是不是列表,Python只但願他是一個可迭代對象,因此這個參數能夠是列表,元組,字符串和生成器!這就是所謂的duck typing
,這也是Python爲什麼如此酷的緣由之一,但這已是另一個問題了......
你能夠在這裏停下,來看一些生成器的高級用法:
>>> 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 # The 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 ...
注意,對於Python 3,請使用 print(corner_street_atm.__next__())
或者 print(next(corner_street_atm))
這在不少場景都很是有用,例如控制資源的獲取。
itertools模塊包含不少處理可迭代對象的特殊方法。曾經想要複製一個生成器嗎?鏈接兩個生成器?用一行代碼將嵌套列表中的值進行分組?不建立另外一個列表進行Map/Zip
?
只須要import itertools
須要一個例子?讓咱們來看看4匹馬賽跑到達終點前後順序的全部可能狀況:
>>> 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)]
迭代是一個實現可迭代對象(實現的是 __iter__() 方法)和迭代器(實現的是 __next__() 方法)的過程。你能夠獲取一個迭代器的任何對象都是可迭代對象,迭代器可讓你迭代遍歷一個可迭代對象(Iterators are objects that let you iterate on iterables.) .
在這篇文章中有關於for
循環如何工做的更多信息:here