翻譯
來源於stackoverflow問答,原文連結 Here
SN上面看到的,順手翻譯下,第一次翻譯,好多地方翻的不是很好 :)
問題:
1 |
Python中yield關鍵字的作用是什麼?它做了什麼? |
例如,我想理解以下程式碼
1 2 3 |
def node._get_child_candidates(self, distance, min_dist, max_dist): if self._leftchild and distance - max_dist = self._median: yield self._rightchild |
下面是呼叫者
1 2 3 4 5 6 7 8 |
result, candidates = list(), [self] while candidates: node = candidates.pop() distance = node._get_dist(obj) if 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的作用,你必須先明白什麼是生成器,在此之前,你需要了解什麼是可迭代物件(可迭代序列)
迭代
你可以建立一個列表,然後逐一遍歷,這就是迭代
1 2 3 4 5 6 |
>>> mylist = [1, 2, 3] >>> for i in mylist: ... print(i) 1 2 3 |
mylist是可迭代的物件,當你使用列表解析時,你建立一個列表,即一個可迭代物件
1 2 3 4 5 6 |
>>> 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)
因為生成器並沒有將所有值放入記憶體中,而是實時地生成這些值
1 2 3 4 5 6 |
>>> 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返回的是一個生成器
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> def createGenerator(): ... mylist = range(3) ... for i in mylist: ... yield i*i ... >>> mygenerator = createGenerator() # create a generator >>> print(mygenerator) # mygenerator is an object! >>> for i in mygenerator: ... print(i) 0 1 4 |
這個例子並沒有什麼實際作用,僅說明當你知道你的函式將產生大量僅被讀取一次的資料時,使用生成器將是十分有效的做法
要掌握yield,你必須明白 – 當你呼叫這個函式,函式中你書寫的程式碼並沒有執行。這個函式僅僅返回一個生成器物件
這有些狡猾 :-)
然後,在每次for迴圈使用生成器時,都會執行你的程式碼
然後,是比較困難的部分:
第一次函式將會從頭執行,直到遇到yield,然後將返回迴圈的首個值. 然後,每次呼叫,都會執行函式中的迴圈一次,返回下一個值,直到沒有值可以返回
當迴圈結束,或者不滿足”if/else”條件,導致函式執行但不命中yield關鍵字,此時生成器被認為是空的
問題程式碼的解釋
生成器:
1 2 3 4 5 6 7 8 9 10 |
# 這你你建立了node的能返回生成器的函式 def node._get_child_candidates(self, distance, min_dist, max_dist): # 這裡的程式碼你每次使用生成器物件都會呼叫 # 如果node節點存在左子節點,且距離沒問題,返回該節點 if self._leftchild and distance - max_dist = self._median: yield self._rightchild # 如果函式執行到這裡,生成器空,該節點不存在左右節點 |
呼叫者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# 建立一個空列表,一個包含當前候選物件引用的列表 result, candidates = list(), [self] # 當前候選非空,迴圈(開始時僅有一個元素) while candidates: # 從候選列表取出最後一個元素作為當前節點 node = candidates.pop() # 獲取obj和當前節點距離 distance = node._get_dist(obj) # 如果距離滿足條件,將節點值加入結果列表 if 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() 接收一個生成器,生成器的所有值被新增到列表中
通常,我們傳一個列表作為引數:
1 2 3 4 5 |
>>> a = [1, 2] >>> b = [3, 4] >>> a.extend(b) >>> print(a) [1, 2, 3, 4] |
但是,在程式碼中,這個函式接受到一個生成器
這樣的做法好處是:
1.你不需要重複讀這些值
2.你可能有海量的子節點,但是不希望將所有節點放入記憶體
並且,可以這麼傳遞生成器作為引數的原因是,Python不關心引數是一個方法還是一個列表
Python接收可迭代物件,對於字串,列表,元組還有生成器,都適用!
這就是所謂的“鴨子型別”(duck typing), 這也是Python如此酷的原因之一, 但這是另一個問題了,對於這個問題……
你可以在這裡完成閱讀,或者讀一點點生成器的進階用法:
控制一個生成器的消耗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
>>> 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()) >>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs >>> print(wall_street_atm.next()) >>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty >>> print(corner_street_atm.next()) >>> 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匹馬賽跑的可能抵達順序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
>>> horses = [1, 2, 3, 4] >>> races = itertools.permutations(horses) >>> print(races) >>> 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 的解釋
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式