[翻译]Python中yield的解释
問題:
Python中yield關鍵字的作用是什么?它做了什么?
例如,我想理解以下代碼
def node._get_child_candidates(self, distance, min_dist, max_dist):if self._leftchild and distance - max_dist < self._median:yield self._leftchildif self._rightchild and distance + max_dist >= self._median:yield self._rightchild
下面是調用者
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
在_get_child_candidates這個函數被調用時發生了什么?返回了一個列表?還是只返回了一個元素?然后又再次被調用?什么時候調用結束?
這段代碼的來源 Jochen Schulz (jrschulz), who made a great Python library for metric spaces. 完整源碼鏈接:?here
要了解yield的作用,你必須先明白什么是生成器,在此之前,你需要了解什么是可迭代對象(可迭代序列)
迭代
你可以創建一個列表,然后逐一遍歷,這就是迭代
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist是可迭代的對象,當你使用列表解析時,你創建一個列表,即一個可迭代對象
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
任何你可用 “for… in…” 處理的都是可迭代對象:列表,字符串,文件…. 這些迭代對象非常便捷,因為你可以盡可能多地獲取你想要的東西
但,當你有大量數據并把所有值放到內存時,這種處理方式可能不總是你想要的 (but you store all the values in memory and it’s not always what you want when you have a lot of values.)
生成器
生成器是迭代器,但你只能遍歷它一次(iterate over them once) 因為生成器并沒有將所有值放入內存中,而是實時地生成這些值
>>> 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
yield是一個關鍵詞,類似return, 不同之處在于,yield返回的是一個生成器
>>> 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
這個例子并沒有什么實際作用,僅說明當你知道你的函數將產生大量僅被讀取一次的數據時,使用生成器將是十分有效的做法
要掌握yield,你必須明白 - 當你調用這個函數,函數中你書寫的代碼并沒有執行。這個函數僅僅返回一個生成器對象
這有些狡猾 :-)
然后,在每次for循環使用生成器時,都會執行你的代碼
然后,是比較困難的部分:
第一次函數將會從頭運行,直到遇到yield,然后將返回循環的首個值. 然后,每次調用,都會執行函數中的循環一次,返回下一個值,直到沒有值可以返回
當循環結束,或者不滿足”if/else”條件,導致函數運行但不命中yield關鍵字,此時生成器被認為是空的
問題代碼的解釋
生成器:
# 這你你創建了node的能返回生成器的函數
def node._get_child_candidates(self, distance, min_dist, max_dist):# 這里的代碼你每次使用生成器對象都會調用# 如果node節點存在左子節點,且距離沒問題,返回該節點
if self._leftchild and distance - max_dist < self._median:yield self._leftchild# 同理,返回右子節點
if self._rightchild and distance + max_dist >= self._median:yield self._rightchild# 如果函數運行到這里,生成器空,該節點不存在左右節點
調用者:
# 創建一個空列表,一個包含當前候選對象引用的列表
result, candidates = list(), [self]# 當前候選非空,循環(開始時僅有一個元素)
while candidates:# 從候選列表取出最后一個元素作為當前節點node = candidates.pop()# 獲取obj和當前節點距離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))# 注意這里extend會反復調用獲取到所有生成器返回值return result
這段代碼包含幾個靈活的部分:
1.這個循環遍讀取歷候選列表,但過程中,候選列表不斷擴展:-)
這是一種遍歷嵌套數據的簡明方法,雖然有些危險,你或許會陷入死循環中
在這個例子中, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) 讀取了生成器產生的所有值, 同時while循環產生新的生成器對象加入到列表,因為每個對象作用在不同節點上,所以每個生成器都將生成不同的值
2.列表方法extend() 接收一個生成器,生成器的所有值被添加到列表中
通常,我們傳一個列表作為參數:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
但是,在代碼中,這個函數接受到一個生成器
這樣的做法好處是:
1.你不需要重復讀這些值
2.你可能有海量的子節點,但是不希望將所有節點放入內存
并且,可以這么傳遞生成器作為參數的原因是,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 # 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
...
這在很多場景都非常有用,例如控制資源的獲取
Itertools
一個很好的工具
itertools模塊包含很多處理可迭代對象的具體方法. 例如
復制一個生成器?連接兩個生成器?一行將嵌套列表中值分組?不使用另一個列表進行Map/Zip? (Ever wish to duplicate a generator? Chain two generators? Group values in a nested list with a one liner? Map / Zip without creating another list?)
只需要使用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.) [好拗口:]
更多關于這個問題的?how does the for loop work
如果你喜歡這個回答,你也許會喜歡我關于?decorators?和?metaclasses?的解釋
總結
以上是生活随笔為你收集整理的[翻译]Python中yield的解释的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 完全理解Python迭代对象、迭代器、生
- 下一篇: python yield 和 yield