生成器-generator

        您可能據說過,帶有 yield 的函數在 Python 中被稱之爲 generator(生成器),何謂 generator ?html

咱們先拋開 generator,以一個常見的編程題目來展現 yield 的概念。node

        如何產生斐波拉契數列?python

斐波那契數列(Fibonacci sequence),又稱黃金分割數列、因數學家列昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖爲例子而引入,故又稱爲「兔子數列」,指的是這樣一個數列:一、一、二、三、五、八、1三、2一、3四、……在數學上,斐波納契數列以以下被以遞歸的方法定義:F(0)=1,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)
程序員

        許多初學者均可以寫出下面的函數:編程

方法一:數據結構

1 # 生成斐波那契數列前n個元素
2 def fab(n): 3     i = 0 4     a, b = 0, 1
5     while i < n: 6         print(a) 7         a, b = b, a + b 8         i += 1

執行 fab(6) 得出前6個元素:app

0 1
1
2
3
5

結果沒有問題,但函數的返回值爲None,別的函數沒法獲取結果,複用性不好,函數

要提升 fab 函數的可複用性,最好不要直接打印出數列,而是返回一個 List。如下是 fab 函數改寫後的第二個版本:spa

方法二:code

 1 # 生成斐波那契數列前n個元素,以list返回
 2 def fab(n):  3     i = 0  4     a, b = 0, 1
 5     list_fab = []  6     while i < n:  7         # print(a)
 8  list_fab.append(a)  9         a, b = b, a + b 10         i += 1
11 
12     return list_fab

可使用以下方式打印出 fab 函數返回的 List:

1 for item in fab(6): 2     print(item)

改寫後的 fab 函數經過返回 List 能知足複用性的要求,可是更有經驗的開發者會指出,該函數在運行中佔用的內存會隨着參數 n的增大而增大,若是要控制內存佔用,最好不要用 List,來保存中間結果,而該經過 iterable 對象來迭代,這時yield就派上用場了。

第三個版本的 fab 和初版相比,僅僅把 print b 改成了 yield b,就在保持簡潔性的同時得到了 iterable 的效果。

方法三:

1 # 使用 yield替代print
2 def fab(n): 3     i = 0 4     a, b = 0, 1
5     while i < n: 6         # print(a)
7         yield a 8         a, b = b, a + b 9         i += 1

簡單地講,yield 的做用就是把一個函數變成一個 generator,帶有 yield 的函數再也不是一個普通函數,Python 解釋器會將其視爲一個 generator,調用 fab(6) 不會執行 fab 函數,而是返回一個 iterable 對象!在 for 循環執行時,每次循環都會執行 fab 函數內部的代碼,執行到 yield a 時,fab 函數就返回一個迭代值,下次迭代時,代碼從 yield a 的下一條語句繼續執行,而函數的本地變量看起來和上次中斷執行前是徹底同樣的,因而函數繼續執行,直到再次遇到 yield。

for item in fab(6): print(item)

也能夠手動調用 fab(6) 的 next() 方法(由於 fab(6) 是一個 generator 對象,該對象具備 next() 方法),這樣咱們就能夠更清楚地看到 fab 的執行流程:

1 f = fab(6) 2 print(next(f)) 3 print(next(f)) 4 print(next(f)) 5 print(next(f)) 6 print(next(f)) 7 print(next(f)) 8 print(next(f))

當函數執行結束時,generator 自動拋出 StopIteration 異常,表示迭代完成。在 for 循環裏,無需處理 StopIteration 異常,循環會正常結束。

咱們能夠得出如下結論:

一個帶有 yield 的函數就是一個 generator,它和普通函數不一樣,生成一個 generator 看起來像函數調用,但不會執行任何函數代碼,直到對其調用 next()(在 for 循環中會自動調用 next())纔開始執行。雖然執行流程仍按函數的流程執行,但每執行到一個 yield 語句就會中斷,並返回一個迭代值,下次執行時從 yield 的下一個語句繼續執行。看起來就好像一個函數在正常執行的過程當中被 yield 中斷了數次,每次中斷都會經過 yield 返回當前的迭代值。

若是想實現一個自定義方法,與range()相似,咱們能夠用生成器來定義它,下面是一個生成範圍內浮點數的生成器:

1 def f_range(start, stop, step):
2     f = start
3     while f < stop:
4         yield f
5         f += step

使用這個函數,咱們能夠用for循環迭代它:

1 for item in f_range(0, 4, 0.5):
2     print(item)
1 0
2 0.5
3 1.0
4 1.5
5 2.0
6 2.5
7 3.0
8 3.5

或者使用其餘接受可迭代對象的方法(內置函數 def sum(iterable, start=None): ,  list(iterable) ):

1 print("sum:", sum(f_range(0, 4, 0.5)))
2 print("list:", list(f_range(0, 4, 0.5)))

探討:

一個函數中須要有一個yield 語句便可將其轉換爲一個生成器。跟普通函數不一樣
的是,生成器只能用於迭代操做

跟普通函數不一樣的是,生成器只能用於迭代操做。下面是一個實驗,向你展現這樣的函數底層工做機

制:

 1 >>> def countdown(n):
 2 ...     while n > 0 :
 3 ...         print("before yield")
 4 ...         yield n
 5 ...         print("after yield")
 6 ...         n -= 1
 7 ...         
 8 >>> iter_c  = countdown(3)  # Create the generator, notice no output appears
 9 >>> print(iter_c, type(iter_c))
10 <generator object countdown at 0x033D0198> <class 'generator'>
11 >>> next(iter_c)  # Run to first yield and emit a value, but doesn't print "after yield"
12 before yield
13 3
14 >>> next(iter_c)  # Run to next yield
15 after yield
16 before yield
17 2
18 >>> next(iter_c)  # Run to next yield
19 after yield
20 before yield
21 1
22 >>> next(iter_c)  # Run to next yield (iteration stops)
23 after yield
24 Traceback (most recent call last):
25   File "<input>", line 1, in <module>
26 StopIteration

一個生成器函數主要特徵是它只會迴應在迭代中使用到的next 操做。一旦生成器
函數返回退出,迭代終止。咱們在迭代中一般使用的for 語句會自動處理這些細節,所
以你無需擔憂。

 

反向迭代:

不少程序員並不知道能夠經過在自定義類上實現reversed () 方法來實現反向迭代。好比:

 1 class CountDown(object):
 2     def __init__(self, start):
 3         self.__start = start
 4 
 5     def __iter__(self):  # Forward iterator
 6         n = self.__start
 7         while n >= 0:
 8             yield n
 9             n -= 1
10 
11     def __reversed__(self):  # Reverse iterator
12         n = 0
13         while n <= self.__start:
14             yield n
15             n += 1
16 
17 c1 = CountDown(5)
18 for item in c1:
19     print(item)
20 
21 for item in reversed(CountDown(5)):
22     print(item)

定義一個反向迭代器可使得代碼很是的高效,由於它再也不須要將數據填充到一個
列表中而後再去反向迭代這個列表。

 

在迭代器一文中 咱們使用Node類表示樹形數據結構,咱們要實現深度優先方式遍歷樹的節點的方法,下面是示例代碼:

 1 class Node(object):
 2     def __init__(self, value):
 3         self.__value = value
 4         self.__children = []
 5 
 6     def add_child(self, node):
 7         self.__children.append(node)
 8 
 9     def set_value(self, value):
10         self.__value = value
11 
12     def get_value(self):
13         return self.__value
14 
15     def __repr__(self):
16         return 'Node({v})'.format(v = self.__value)
17 
18     def __iter__(self):
19         return iter(self.__children)
20 
21     def depth_first(self):
22         yield self
23         for child in self:  # Invokes self.__iter__()
24             yield from child.depth_first()  # 將yield from視爲提供了一個調用者和子生成器之間的透明的雙向通道。包括從子生成器獲取數據以及向子生成器傳送數據。
# Example
root = Node(10)
n2 = Node(20)
n3 = Node(30)

root.add_child(n2)
root.add_child(n3)
n2.add_child(Node(40))
n2.add_child((Node(50)))
n3.add_child((Node(60)))

for chi in root.depth_first():
    print(chi)

先序遍歷過程:

因此最後的輸出是:Node(10) Node(20) Node(40) Node(50) Node(30) Node(60)

 

 迭代器切片:

函數itertools.islice() 正好適用於在迭代器和生成器上作切片操做

def count(n):
    while True:
        yield n
        n += 1

it_c = count(0)
it_c[10, 20]

#Output:
#it_c[10, 20]
#TypeError: 'generator' object is not subscriptable

#Use islice
from itertools import islice
for i in islice(count(1), 10, 20):
    print(i)

探討:迭代器和生成器不能使用標準的切片操做,由於它們的長度事先咱們並不知道(而且也沒有實現索引)。函數islice() 返回一個能夠生成指定元素的迭代器,它經過遍歷並丟棄

直到切片開始索引位置的全部元素。而後纔開始一個個的返回元素,並直到切片結束索引位置。這裏要着重強調的一點是islice() 會消耗掉傳入的迭代器中的數據。必須考慮到迭代器是不可逆的這個事實。因此若是你須要以後再次訪問這個迭代器的話,那你就得先將它裏面的數據放入一個列表中。

 

#Note:本文參考另一篇博文

原文連接:http://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

相關文章
相關標籤/搜索