Python 迭代器 深入理解 與應用示例
阿新 • • 發佈:2019-02-15
本篇文章簡單談談可迭代物件,迭代器和生成器之間的關係。
三者簡要關係圖
可迭代物件與迭代器
剛開始我認為這兩者是等同的,但後來發現並不是這樣;下面直接丟擲結論:
1)可迭代物件包含迭代器。2)如果一個物件擁有__iter__方法,其是可迭代物件;如果一個物件擁有next方法,其是迭代器。
3)定義可迭代物件,必須實現__iter__方法;定義迭代器,必須實現__iter__和next方法。
你也許會問,結論3與結論2是不是有一點矛盾?既然一個物件擁有了next方法就是迭代器,那為什麼迭代器必須同時實現兩方法呢?
因為結論1,迭代器也是可迭代物件,因此迭代器必須也實現__iter__方法。介紹一下上面涉及到的兩個方法:
1)__iter__()
該方法返回的是當前物件的迭代器類的例項。因為可迭代物件與迭代器都要實現這個方法,因此有以下兩種寫法。
寫法一:用於可迭代物件類的寫法,返回該可迭代物件的迭代器類的例項。
寫法二:用於迭代器類的寫法,直接返回self(即自己本身),表示自身即是自己的迭代器。
也許有點暈,沒關係,下面會給出兩寫法的例子,我們結合具體例子看。
2)next()返回迭代的每一步,實現該方法時注意要最後超出邊界要丟擲StopIteration異常。
下面舉個可迭代物件與迭代器的例子:
- #!/usr/bin/env python
- # coding=utf-8
- class MyList(object):
- def __init__(self, num):
- self.data = num # 上邊界
- def __iter__(self):
- return MyListIterator(self.data) # 返回該可迭代物件的迭代器類的例項
- class MyListIterator(object): # 定義迭代器類,其是MyList可迭代物件的迭代器類
- def __init__(self, data):
- self.data = data # 上邊界
- self
- def __iter__(self):
- returnself# 返回該物件的迭代器類的例項;因為自己就是迭代器,所以返回self
- def next(self): # 迭代器類必須實現的方法
- whileself.now < self.data:
- self.now += 1
- returnself.now - 1# 返回當前迭代值
- raise StopIteration # 超出上邊界,丟擲異常
- my_list = MyList(5) # 得到一個可迭代物件
- print type(my_list) # 返回該物件的型別
- my_list_iter = iter(my_list) # 得到該物件的迭代器例項,iter函式在下面會詳細解釋
- print type(my_list_iter)
- for i in my_list: # 迭代
- print i
執行結果:
其實該函式與迭代是息息相關的,通過在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方法),不需要再手動實現兩方法。
生成器在迭代的過程中可以改變當前迭代值,而修改普通迭代器的當前迭代值往往會發生異常,影響程式的執行。
看一個生成器的例子:
- #!/usr/bin/env python
- # coding=utf-8
- def myList(num): # 定義生成器
- now = 0# 當前迭代值,初始為0
- while now < num:
- val = (yield now) # 返回當前迭代值,並接受可能的send傳送值;yield在下面會解釋
- now = now + 1if val isNoneelse val # val為None,迭代值自增1,否則重新設定當前迭代值為val
- my_list = myList(5) # 得到一個生成器物件
- print my_list.next() # 返回當前迭代值
- print my_list.next()
- my_list.send(3) # 重新設定當前的迭代值
- print my_list.next()
- 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