摘要:生成器生成器是迭代器,但是只能迭代一次,生成器不會將所有值存儲在內存中,而是實時的生成這些值看上去除了用替換了原來的外,它們沒什么不同。
這是stackoverflow上一個關于python中yield用法的帖子,這里翻譯自投票最高的一個回答,原文鏈接 here
問題Python中yield關鍵字的用途是什么?它有什么作用?
例如,我試圖理解以下代碼 ¹:
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):
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.
回答要想理解yield的作用,你必須了解什么是生成器(generators),在這之前,我們先來看可迭代對象(iterables)。
可迭代對象 (iterables)當你創建了一個列表,你可以遍歷這個列表讀取它的每一個元素,逐個讀取列表元素稱為迭代(iteration)。
>>> mylist = [1, 2, 3] >>> for i in mylist: ... print(i) 1 2 3
mylist就是一個可迭代對象(iterable)。當你使用列表生成式(list comprehension)創建一個列表(list),即創建了一個可迭代對象。
>>> 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.)。
生成器是迭代器(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,逐一生成。
yieldyield 是一個類似 return 的關鍵字,不同的是這個函數將返回一個生成器。
>>> 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
這個例子沒有什么實際作用。但是當你知道你的函數將返回大量你只需要讀取一次的值時,使用生成器是一個有效的做法。
要掌握 yeild,你必須要知道當你調用這個函數時,你在函數體中編寫的代碼并沒有立馬執行。
該函數僅僅返回一個生成器對象,這有點棘手 :-)
然后,你的代碼將從for循環每次使用生成器停止的位置繼續執行。
現在到了關鍵部分:
for第一次調用從函數創建的生成器對象,函數將從頭開始執行直到遇到yeild,然后返回yield后的值作為第一次迭代的返回值。接下來每次調用都會再次執行你在函數中定義的循環,并返回(return)下一個值,直到沒有值可以返回(return)。
當循環結束,或者不滿足if/else條件,導致函數運行但不會執行(not hit)yeild,此時生成器被認為是空的。
問題代碼的解釋 (Your code explained)生成器 (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為何如此酷的原因之一,但這已經是另外一個問題了......
你可以在這里停下,來看一些生成器的高級用法:
控制生成器的窮盡 (Controlling a generator exhaustion)>>> 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 # The 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 ...
注意,對于Python 3,請使用 print(corner_street_atm.__next__()) 或者 print(next(corner_street_atm))
這在很多場景都非常有用,例如控制資源的獲取。
Itertools,你最好的朋友 (Itertools, your best friend)itertools模塊包含很多處理可迭代對象的特殊方法。曾經想要復制一個生成器嗎?連接兩個生成器?用一行代碼將嵌套列表中的值進行分組?不創建另一個列表進行Map/Zip?
只需要import itertools
需要一個例子?讓我們來看看4匹馬賽跑到達終點先后順序的所有可能情況:
>>> horses = [1, 2, 3, 4] >>> races = itertools.permutations(horses) >>> print(races)了解迭代的內部機制 (Understanding the inner mechanisms of iteration)>>> 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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/42825.html
摘要:在上一篇如何給列表降維函數的妙用中,我們介紹了這個用法,還對函數做了擴展的學習。是的,函數做列表降維有奇效,但它性能堪憂,并不是最好的選擇。這正是函數出于一致性考慮,而舍棄掉的實現方案。 showImg(https://segmentfault.com/img/remote/1460000019004608?w=5184&h=2916); 本文原創并首發于公眾號【Python貓】,未經...
摘要:當運行到時,不會暫停,而是直接跳進函數執行函數內的代碼。由于函數中沒有,因此會一直執行完函數中的代碼,并返回至函數中執行后面的代碼。 本系列旨在通過對co,koa等庫源碼的研究,進而理解generator在異步編程中的重大作用(ps:所有代碼請在node --harmony或者iojs環境中運行) koa中間件的形式 相信用過koa的小伙伴一定很熟悉下面這段代碼 var app ...
摘要:搞這么神秘其實就是個迭代器的核心實際上就是一個,通過關鍵字能夠把函數體拆成完全可控執行片段,在函數體外部通過來對這些執行片段進行遍歷這和遍歷這些數據結構是一個道理只不過用來遍歷函數片段,而用來遍歷元素對生成器執行操作,進行生成器的入口開始執 Generator 搞這么神秘 其實就是個迭代器 Generator的核心實際上就是一個Iterator,通過yield關鍵字能夠把函數體拆成完全...
摘要:搞這么神秘其實就是個迭代器的核心實際上就是一個,通過關鍵字能夠把函數體拆成完全可控執行片段,在函數體外部通過來對這些執行片段進行遍歷這和遍歷這些數據結構是一個道理只不過用來遍歷函數片段,而用來遍歷元素對生成器執行操作,進行生成器的入口開始執 Generator 搞這么神秘 其實就是個迭代器 Generator的核心實際上就是一個Iterator,通過yield關鍵字能夠把函數體拆成完全...
閱讀 1954·2021-11-19 09:40
閱讀 2145·2021-10-09 09:43
閱讀 3300·2021-09-06 15:00
閱讀 2818·2019-08-29 13:04
閱讀 2773·2019-08-26 11:53
閱讀 3535·2019-08-26 11:46
閱讀 2328·2019-08-26 11:38
閱讀 396·2019-08-26 11:27