Python高級語法中,由一個yield
關鍵詞生成的generator
生成器,是精髓中的精髓。它雖然比裝飾器、魔法方法更難懂,可是它強大到咱們不可思議的地步:小到簡單的for loop循環,大到代替多線程作服務器的高併發處理,均可以基於yield
來實現。python
簡單來講,yield
是代替return
的另外一種方案:服務器
return
就像人只有一生,一個函數一旦return,它的生命就結束了yield
就像有「第二人生」、「第三人生」甚至輪迴轉世同樣,函數不但能返回值,「重生」之後還能再接着「上輩子」的記憶繼續返回值個人定義:yield
在循環中代替return
,每次循環返回一次值,而不是所有循環完了才返回值。多線程
yield怎麼念?併發
return咱們念「返回xx值」,我建議:yield能夠更形象的念爲"嘔吐出xx值「,每次嘔一點。
通常咱們進行循環迭代的時候,都必須等待循環結束後才return結果。
數量小的時候還行,可是若是循環次數上百萬?上億?咱們要等多久?
若是循環中不涉及I/O還行,可是若是涉及I/O堵塞,一個堵幾秒,後邊幾百萬個客戶等着呢,銀行櫃檯還能不能下班了?app
因此這裏確定是要並行處理
的。除了傳統的多線程多進程外,咱們還能夠選擇Generator生成器,也就是由yield
代替return,每次循環都返回值,而不是所有循環完了才返回結果。函數
這樣作的好處就是——極大的節省了內存。若是用return,那麼循環中的全部數據都要不斷累計到內存裏直到循環結束,這個不友好。
而yield則是一次一次的返回結果,就不會在內存裏累加了。因此數據量越大,優點就越明顯。高併發
有多明顯?若是作一百萬的簡單數字計算,普通的for loop return會增長300MB+的內存佔用!而用yield一次一次返回,增長的內存佔用幾乎爲0MB!oop
既然yield
不是所有循環完了再返回,而是循環中每次都返回,因此位置天然不是在for loop以後,而是在loop之中。性能
先來看通常的for loop返回:線程
def square(numbers): result = [] for n in numbers: result.append( n**2 ) return result #在for以外
再來看看yield怎麼作:
def square(numbers): for n in numbers: yield n**2 #在for之中
能夠看到,yield在for loop之中,且函數徹底不須要寫return返回。
這時候若是你print( square([1,2,3]) )
獲得的就不是直接的結果,而是一個<generator object>
。
若是要使用,就必須一次一次的next(...)
來獲取下一個值:
>>> results = square( [1,2,3] ) >>> next( result ) 1 >>> next( result ) 4 >>> next( result ) 9 >>> next( result ) ERROR: StopIteration
這個時候更簡單的作法是:
for r in results: print( r )
由於in
這個關鍵詞自動在後臺爲咱們調用生成器的next(..)
函數
什麼是generator生成器?
只要咱們在一個函數中用了yield
關鍵字,函數就會返回一個<generator object>
生成器對象,二者是相輔相成的。有了這個對象後,咱們就可使用一系列的操做來控制這個循環結果了,好比next(..)
獲取下一個迭代的結果。
yield
和generator
的關係,簡單來講就是一個原由一個結果:只要寫上yield, 其所在的函數就立馬變成一個<generator object>
對象。
Python中咱們使用range()
函數生成數列很是經常使用。而xrange()
的使用方法、效果幾乎如出一轍,惟一不一樣的就是——xrange()
返回的是生成器,而不是直接的結果。
若是數據量大時,xrange()
能極大的減少內存佔用,帶來卓越的性能提高。
固然,幾百、幾千的數量級,就直接用range好了。
有時候咱們可能會在一個函數中、或者一個for loop中看到多個yield
,這有點不太好理解。
但其實很簡單!
通常狀況下,咱們寫的:
for n in [1,2,3]: yield n**2
實際上它的本質是生成了這個東西:
yield 1**2 yield 2**2 yield 3**2
也就是說,不用for loop,咱們本身手寫一個一個的yield,效果也是同樣的。
你每次調用一次next(..)
,就獲得一個yield後面的值。而後三個yield的第一個就會被劃掉,剩兩個。再調用一次,再劃掉一個,就剩一個。直到一個都不剩,next(..)
就返回異常。
一旦瞭解這個本質,咱們就能理解一個函數裏寫多個yield是什麼意思了。
從多重yield延伸,咱們能夠開始更進一步瞭解yield到底作了些什麼了。
如今,咱們不把yield看做是return的替代品了,而是把它看做是一個suspense
暫停符。
即每次程序遇到yield,都會暫停。當你調用next(..)
時候,它再resume
繼續。
好比咱們改一下上面的程序:
def func(): yield 1**2 print('Hi, Im A!') yield 2**2 print('Hi, Im B!') yield 3**2 print('Hi, Im C!')
而後咱們調用這個小函數,來看看yield產生的實際效果是什麼:
>>> f = func() >>> f <generator object func at 0x10d36c840> >>> next( f ) 1 >>> next( f ) Hi, Im A! 4 >>> next( f ) Hi, Im B! 9 >>> next( f ) Hi, Im C! ERROR: StopIteration
從這裏咱們能夠看到:
因此到了這裏,若是咱們能理解yield做爲暫停符
的做用,就能夠很是靈活的用起來了。
yield from
與sub-generator
子生成器yield from
是Python 3.3開始引入的新特性。
它主要做用就是:當我須要在一個生成器函數
中使用另外一個生成器時,能夠用yield from
來簡化語句。
舉例,正常狀況下咱們可能有這麼兩個生成器,第二個調用第一個:
def gen1(): yield 11 yield 22 yield 33 def gen2(): for g in gen1(): yield g yield 44 yield 55 yield 66
能夠看到,咱們在gen2()
這個生成器中調用了gen1()
的結果,並把每次獲取到的結果yield轉發出去,當成本身的yield出來的值。
咱們把這種一個生成器中調用的另外一個生成器
叫作sub-generator
子生成器,而這個子生成器由yield from
關鍵字生成。
因爲sub-generator
子生成器很經常使用,因此Python引入了新的語法來簡化這個代碼:yield from
。
上面gen2()
的代碼能夠簡化爲:
def gen2(): yield from gen1() yield 44 yield 55 yield 66
這樣看起來是否是更"pythonic"了呢?:)
因此只要記住:yield from
只是把別人嘔吐出來的值,直接當成本身的值嘔吐出去。
通常咱們只是二選一:要否則遞歸,要否則for循環中yield。有時候yield就能夠解決遞歸的問題,可是有時候光用yield並不能解決,仍是要用遞歸。
那麼怎麼既用到遞歸,又用到yield生成器呢?
def func(n): result = n**2 yield result if n < 100: yield from func( result ) for x in func(100): print( x )
上面代碼的邏輯是:若是n小於100,那麼每次調用next(..)
的時候,都獲得n的乘方。下次next,會繼續對以前的結果進行乘方,直到結果超過100爲止。
咱們看到代碼裏利用了yield from
子生成器。由於yield出的值不是直接由變量來,而是由「另外一個」函數得來了。