1. 程式人生 > >Python 迭代器 深入理解 與應用示例

Python 迭代器 深入理解 與應用示例

本篇文章簡單談談可迭代物件,迭代器和生成器之間的關係。

三者簡要關係圖




可迭代物件與迭代器

剛開始我認為這兩者是等同的,但後來發現並不是這樣;下面直接丟擲結論:

1)可迭代物件包含迭代器。
2)如果一個物件擁有__iter__方法,其是可迭代物件;如果一個物件擁有next方法,其是迭代器。
3)定義可迭代物件,必須實現__iter__方法;定義迭代器,必須實現__iter__和next方法。


你也許會問,結論3與結論2是不是有一點矛盾?既然一個物件擁有了next方法就是迭代器,那為什麼迭代器必須同時實現兩方法呢?

因為結論1,迭代器也是可迭代物件,因此迭代器必須也實現__iter__方法。

介紹一下上面涉及到的兩個方法:

1)__iter__()

該方法返回的是當前物件的迭代器類的例項。因為可迭代物件與迭代器都要實現這個方法,因此有以下兩種寫法。

寫法一:用於可迭代物件類的寫法,返回該可迭代物件的迭代器類的例項。

寫法二:用於迭代器類的寫法,直接返回self(即自己本身),表示自身即是自己的迭代器。

也許有點暈,沒關係,下面會給出兩寫法的例子,我們結合具體例子看。

2)next()

返回迭代的每一步,實現該方法時注意要最後超出邊界要丟擲StopIteration異常。

下面舉個可迭代物件與迭代器的例子:

  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. class MyList(object):            
    # 定義可迭代物件類
  4.     def __init__(self, num):  
  5.         self.data = num          # 上邊界
  6.     def __iter__(self):  
  7.         return MyListIterator(self.data)  # 返回該可迭代物件的迭代器類的例項
  8. class MyListIterator(object):    # 定義迭代器類,其是MyList可迭代物件的迭代器類
  9.     def __init__(self, data):  
  10.         self.data = data         # 上邊界
  11.         self
    .now = 0# 當前迭代值,初始為0
  12.     def __iter__(self):  
  13.         returnself# 返回該物件的迭代器類的例項;因為自己就是迭代器,所以返回self
  14.     def next(self):              # 迭代器類必須實現的方法
  15.         whileself.now < self.data:  
  16.             self.now += 1
  17.             returnself.now - 1# 返回當前迭代值
  18.         raise StopIteration      # 超出上邊界,丟擲異常
  19. my_list = MyList(5)              # 得到一個可迭代物件
  20. print type(my_list)              # 返回該物件的型別
  21. my_list_iter = iter(my_list)     # 得到該物件的迭代器例項,iter函式在下面會詳細解釋
  22. print type(my_list_iter)  
  23. for i in my_list:                # 迭代
  24.     print i  

執行結果:


問題:上面的例子中出現了iter函式,這是什麼東西?和__iter__方法有關係嗎?
其實該函式與迭代是息息相關的,通過在Python命令列中列印“help(iter)”得知其有以下兩種用法。

用法一:iter(callable, sentinel)
不停的呼叫callable,直至其的返回值等於sentinel。其中的callable可以是函式,方法或實現了__call__方法的例項。

用法二:iter(collection)
1)用於返回collection物件的迭代器例項,這裡的collection我認為表示的是可迭代物件,即該物件必須實現__iter__方法;事實上iter函式與__iter__方法聯絡非常緊密,iter()是直接呼叫該物件的__iter__(),並把__iter__()的返回結果作為自己的返回值,故該用法常被稱為“建立迭代器”。
2)iter函式可以顯示呼叫,或當執行“for i in obj:”,Python直譯器會在第一次迭代時自動呼叫iter(obj),之後的迭代會呼叫迭代器的next方法,for語句會自動處理最後丟擲的StopIteration異常。

通過上面的例子,相信對可迭代物件與迭代器有了更具體的認識,那麼生成器與它們有什麼關係呢?下面簡單談一談


生成器

生成器是一種特殊的迭代器,生成器自動實現了“迭代器協議”(即__iter__和next方法),不需要再手動實現兩方法。

生成器在迭代的過程中可以改變當前迭代值,而修改普通迭代器的當前迭代值往往會發生異常,影響程式的執行。

看一個生成器的例子:

  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. def myList(num):      # 定義生成器
  4.     now = 0# 當前迭代值,初始為0
  5.     while now < num:  
  6.         val = (yield now)                      # 返回當前迭代值,並接受可能的send傳送值;yield在下面會解釋
  7.         now = now + 1if val isNoneelse val  # val為None,迭代值自增1,否則重新設定當前迭代值為val
  8. my_list = myList(5)   # 得到一個生成器物件
  9. print my_list.next()  # 返回當前迭代值
  10. print my_list.next()  
  11. my_list.send(3)       # 重新設定當前的迭代值
  12. print my_list.next()  
  13. print dir(my_list)    # 返回該物件所擁有的方法名,可以看到__iter__與next在其中

執行結果:



具有yield關鍵字的函式都是生成器,yield可以理解為return,返回後面的值給呼叫者。不同的是return返回後,函式會釋放,而生成器則不會。在直接呼叫next方法或用for語句進行下一次迭代時,生成器會從yield下一句開始執行,直至遇到下一個yield。

#生成器函式,函式裡只要有yield關鍵字

def gen_func():
    yield 1
    yield 2
    yield 3


def fib(index):
    if index <= 2:
        return 1
    else:
        return fib(index-1) + fib(index-2)


def fib2(index):
    re_list = []
    n,a,b = 0,0,1
    while n<index:
        re_list.append(b)
        a,b = b, a+b
        n += 1
    return re_list


def gen_fib(index):
    n,a,b = 0,0,1
    while n<index:
        yield b
        a,b = b, a+b
        n += 1


for data in gen_fib(10):
    print (data)
# print (gen_fib(10))
# 斐波拉契 0 1 1 2 3 5 8
#惰性求值, 延遲求值提供了可能


def func():
    return 1


if __name__ == "__main__":
    #生成器物件, python編譯位元組碼的時候就產生了,
    gen = gen_func()
    for value in gen:
        print (value)
    # re = func()
    # pass