首頁 >後端開發 >Python教學 >深入理解Python中的生成器

深入理解Python中的生成器

黄舟
黄舟原創
2016-12-16 11:36:541688瀏覽

生成器(generator)概念

生成器不會把結果保存在一個系列中,而是保存生成器的狀態,在每次進行迭代時返回一個值,直到遇到StopIteration異常結束。

生成器語法

生成器表達式: 通列表解析語法,只不過把列表解析的[]換成()
生成器表達式能做的事情列表解析基本都能處理,只不過在需要處理的序列比較大時,列表解析比較費記憶體。

>>> gen = (x**2 for x in range(5))
>>> gen
at 0x0000000002FB7B40>
>> for gg in gen: print g, end='-')
...
0-1-4-9-16-
>>> for x in [0,1,2,3,4,5]:
...   print( x, end='-')
...
0-1-2-3-4-5-

產生器函數: 在函數中如果出現了yield關鍵字,那麼該函數就不再是普通函數,而是生成器函數。

但是生成器函數可以生產一個無線的序列,這樣列表根本沒有辦法處理。
yield 的作用就是把一個函數變成一個 generator,帶有 yield 的函數不再是一個普通函數,Python 解釋器會將其視為一個 generator。

下面為一個可以無窮生產奇數的生成器函數。

def odd():

   n=1
   while True:
       yield n
    if count >=5: break
   print(o )
   count +=1


當然透過手動編寫迭代器可以實現類似的效果,只不過生成器更直觀易懂

class Iter:
   def __init__(self):

class Iter:

   def = __iter__(self):

       return self

   def __next__(self):
       self.start +=2
         self.start +=2
    區
   print(next(I))


題外話: 生成器是包含有__iter()和next__()方法的,所以可以直接使用for來迭代,而沒有包含StopIteration的自編Iter來只能透過手動循環來迭代。

>>> from collections import Iterable
>>> from collections import Iterator
>>> isinstance(odd_num, Iterable)

True

>>> isodnce(odd_num, Iter)

True odd_num

True
>>> help(odd_num)
Help on generator object:

odd = class generator(object)
|  Methods defined here:
| 🠎 | ter(self).
|
|  __next__(self, /)
|      Implement next(self).
......


看到上面的結果,現在你可以很有信心的按照Iterator的方式進行循環了吧!

在for 迴圈執行時,每次迴圈都會執行fab 函數內部的程式碼,當執行到yield b 時,fab 函數會傳回一個迭代值,下次迭代時,程式碼從yield b 的下一語句繼續執行,而函數的本地變數看起來和上次中斷執行前是完全一樣的,所以函數繼續執行,直到再次遇到yield。看起來好像一個函數在正常執行的過程中被 yield 中斷了數次,每次中斷都會透過 yield 傳回目前的迭代值。

yield 與return

在一個生成器中,如果沒有return,則預設執行到函數完成時返回StopIteration;

>>>> def g1():

...    yield 1

>

> > g=g1()

>>> next(g)    #第一次呼叫next(g)時,會在執行完yield語句後掛起,所以此時程式並沒有執行結束。

1

>>> next(g)    #程式試圖從yield語句的下一語句開始執行,發現已經到了結尾,所以拋出StopIteration異常。

Traceback (most recent call last):

 File "", line 1, in

StopIteration
>>>>


如果遇到return,如果在執行過程中returnreturnation終止迭代。

>>> def g2():
...     yield 'a'
...     return
...     yield 'b'
...
>>> g=g2()
>> next
)    #程式停留在執行完yield 'a'語句後的位置。
'a'
>>> next(g)    #程式發現下一語句是return,所以拋出StopIteration異常,這樣yield 'b'語句永遠也不會執行。
Traceback (most recent call last):
 File "", line 1, in
StopIteration

如果在return後回傳一個值,那麼這個異常值為StopIteration值。

產生器沒有辦法使用return來傳回值。


>>> def g3():
...     yield 'hello'
...     return 'world'
...
>>> g=g3(ello)
>>> next(g)
>>> g=g3(ello)
>>> next(g)
')
>> '
>>> next(g)

Traceback (most recent call last):

 File "", line 1, in

StopIteration: world    



生成器支援的方法
> help(odd_num)
Help on generator object:

odd = class generator(object)
|  Methods defined here:
...... 我 | generator.
|
|  send(...)
|      send(arg) -> send 'arg' into generator,
|      return next yielded value
|      return next yielded value 的 throw (typ[,val[,tb]]) -> raise exception in generator,
|      return next yielded value or raise StopIteration.
......

close()

後函數的呼叫會直接回傳StopIteration異常。

>>> def g4():

...     yield 1

...     yield 2
...     yield 3
...
>>> g=g4> 1
>>> g.close()
>>> next(g)    #關閉後,yield 2和yield 3語句將不再起作用
Traceback (most recent call last):
 File "", line 1, in
StopIteration


send()

生成器函數最大的特徵是可以接受外部傳入的一個變量,並根據變量內容計算結果後返回。

這是生成器函數最難理解的地方,也是最重要的地方,實現後面我會講到的協程就全靠它了。

def gen():

   value=0
   while True:

       receive=yielield value       receive=yiel        value = 'got: %s' % receive


g=gen()
print(g.send(None))    
print(g.send('aaa'))
print(g.send(3))
print(g.send('e'))


執行流程:

透過g.send(None)或next(g)可以啟動生成器函數,並執行到第一個yield語句結束的位置。
此時,執行完了yield語句,但是沒有給receive賦值。
yield value會輸出初始值0
注意:在啟動產生器函數時只能send(None),如果試圖輸入其它的值都會得到錯誤提示訊息。

通過g.send('aaa'),會傳入aaa,並賦值給receive,然後計算value的值,並回到while頭部,執行yield value語句有停止。

此時yield value會輸出"got: aaa",然後掛起。


通過g.send(3),會重複第2步,最後輸出結果為"got: 3"

當我們g.send('e')時,程式會執行break然後推出循環,最後整個函數執行完畢,所以會得到StopIteration異常。
最後的執行結果如下:

0

got: aaa
got: 3

Traceback (most recent call last):

File "h.py", line 14, in

File "h.py", line 14, in

 print' e'))
StopIteration


throw()

用來向生成器函數送入一個異常,可以結束系統定義的異常,或自訂的異常。
throw()後直接跑出異常並結束程序,或者消耗掉一個yield,或者在沒有下一個yield的時候直接進行到程序的結尾。

def gen():
   while True:
       try:
           yield 'normal value'
       yield 'normal value'
       y       print('here')
       except ValueError:
           print('we got  Error here')🠎 :
           break

g=gen()
print(next(g))
print(g.throw(ValueError))
print(next(g))
print(g.throw()
結果為:

normal value

we got ValueError here

normal value

normal value 2
Traceback (most recent call last):
 File "h.py", line 15, in StopIteration


解釋:

print(next(g)):會輸出normal value,並停留在yield 'normal value 2'之前。

由於執行了g.throw(ValueError),所以會跳過所有後續的try語句,也就是說yield 'normal value 2'不會被執行,然後進入到except語句,打印出we got ValueError here。

然後再次進入到while語句部分,消耗一個yield,所以會輸出normal value。

print(next(g)),會執行yield 'normal value 2'語句,並停留在執行完該語句後的位置。

g.throw(TypeError):會跳出try語句,從而print('here')不會被執行,然後執行break語句,跳出while循環,然後到達程式結尾,所以跑出StopIteration異常。


下面給出一個綜合例子,用來把一個多維列表展開,或者說扁平化多維列表)

def flatten(nested):

   

   try:

     

   try:
     
       if isinstance(nested, str):
           raise TypeError
       for sublist inbstedsted:a.           for element in flatten(sublist):
               #yield element類型Error:
       #print('here')
       yield nested
       
L=['aaadf',[1,2,35],2,48,48,8 '],7]]
for num in flatten(L):
   print(num)


如果理解起來有點困難,那麼把print語句的註釋打開在進行查看就比較明了。

yield from

yield產生的函數就是一個迭代器,所以我們通常會把它放在循環語句中進行輸出結果。

有時候我們需要把這個yield產生的迭代器放在另一個生成器函數中,也就是生成器巢狀。

例如下面的例子:

def inner():

   for i in range(10):

       yield i
def outer():
   yield i

def outer():

   
       res = g_inner.send(None)
       yield res

g_outer=outer()
while True:
   try:
    Stop.       break


此時,我們可以採用yield from語句來減少我麼你的工作量。

def outer2():
   yield from inner()

當然,yield from語句的重點是幫我們自動處理內外層之間的異常問題,這裡有2篇寫的很好的文章,所以我就不再囉嗦了。

http://blog.theerrorlog.com/yield-from-in-python-3.html

http://stackoverflow.com/questions/9708902/in-practice-what-are-the-main-uses-for -the-new-yield-from-syntax-in-python-3


總結

依照鴨子模型理論,生成器就是一種迭代器,可以使用for進行迭代。



第一次執行next(generator)時,會執行完yield語句後程式進行掛起,所有的參數和狀態會進行保存。

再一次執行next(generator)時,會從掛起的狀態開始往後執行。

在遇到程式的結尾或遇到StopIteration時,循環結束。


可以透過generator.send(arg)來傳入參數,這是協程模型。


可以透過generator.throw(exception)來傳入一個異常。 throw語句會消耗掉一個yield。
可以透過generator.close()來手動關閉生成器。

next()等價於send(None)

 以上就是深入理解Python中的生成器的內容,更多相關文章請關注PHP中文網(www.php.cn)!



陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
上一篇:Python直與編碼下一篇:Python直與編碼