python-迭代器協議和for迴圈工作機制
一、遞迴與迭代
二、什麼是迭代器協議
1、迭代器協議是指:物件必須提供一個next方法,執行該方法要麼返回迭代中的下一項,要麼就引起一個stopiteration異常,已終止迭代(只能往後走不能往前退)
2、可迭代物件:實現了迭代器協議的物件(如何實現:物件內部定義一個__iter__()方法)
3、協議是一種約定,可迭代物件實現了迭代器協議,python的內部工具(如for迴圈,sum,min,max函式等)使用迭代器協議訪問物件。
三、python中強大的for迴圈機制
for迴圈的本質:迴圈所有物件,全部是使用迭代器協議
解釋:
有時會想,for迴圈的本質就是遵循迭代器協議訪問物件,那麼for迴圈的物件肯定都是迭代器了啊,沒錯,那既然這樣,for迴圈可以遍歷(字串,,列表,字典,集合,檔案物件),那這些型別的資料肯定都是可迭代物件啊?但是,為什麼定義一個列表l=[1,2,3,4]沒有next()方法。
(字串,列表,元組,字典,集合,檔案物件)這些都不是可迭代物件,只不過在for迴圈中,呼叫了他們內部的__iter__方法,把他們變成了可迭代物件
然後for迴圈呼叫可迭代物件的__next__方法去取值,而且for迴圈會捕捉stoplteration異常,已終止迭代
l=[1,2,3,4,5] #下標訪問方式 print(l[0]) print(l[7]) #超出訪問會報IndexError: list index out of range #遵循迭代器協議的方式 diedai=l.__iter__() print(diedai.__next__()) print(diedai.__next__()) print(diedai.__next__()) print(diedai.__next__()) print(diedai.__next__()) print(diedai.__next__()) #超出邊界會報StopIteration #for迴圈訪問方式: #for迴圈本質就是遵循迭代器協議的訪問方式,先呼叫diedai.__iter__()方法,或者直接diedai=iter(l),然後依次執行diedai.next(),直到for迴圈捕捉到StopIteration終止迴圈 #for迴圈所有物件的本質都是一樣的道理 for i in l: #diedai=l.__iter__()print(l[i]) #i=diedai.next() #使用while模擬for迴圈做的事情 diedai_l=l.__iter__() while True: try: print(diedai_l.__next__()) except StopIteration: print("迭代完畢,終止迴圈") break
四、生成器初探
什麼是生成器?
可以理解為一種資料型別,這種資料型別自動實現了迭代器協議(其他的資料型別需要呼叫自己內建的__iter__方法),所以生成器就是可迭代物件
生成器分類及在python中的表現形式:(python有兩種不同的方法提供生成器)
1、生成器函式:常規函式定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在沒個結果中間,掛起函式的狀態,以便下次用它離開的地方繼續執行
2、生成器表示式:類似於列表推導,但是,生成器返回按需產生結果的一個物件,而不是一次構建一個結果列表
為何使用生成器以及生產器的優點:
python使用生成器對延遲操作提供了支援,所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果,這也是生產器的重要好處
import time # def producer(): # ret=[] # for i in range(100): # time.sleep(0.1) # ret.append('包子%s' %i) # return ret # # def consumer(res): # for index,baozi in enumerate(res): # time.sleep(0.1) # print('第%s個人,吃了%s' %(index,baozi)) # # res=producer() # consumer(res) #yield 3相當於return 控制的是函式的返回值 #x=yield的另外一個特性,接受send傳過來的值,賦值給x # def test(): # print('開始啦') # firt=yield #return 1 first=None # print('第一次',firt) # yield 2 # print('第二次') # # t=test() # res=t.__next__() #next(t) # print(res) # # t.__next__() # # res=t.send(None) # res=t.send('函式停留在first那個位置,我就是給first賦值的') # print(res) # def producer(): # ret=[] # for i in range(100): # time.sleep(0.1) # ret.append('包子%s' %i) # return ret def consumer(name): print('我是[%s],我準備開始吃包子了' %name) while True: baozi=yield time.sleep(1) print('%s 很開心的把【%s】吃掉了' %(name,baozi)) def producer(): c1=consumer('wupeiqi') c2=consumer('yuanhao_SB') c1.__next__() c2.__next__() for i in range(10): time.sleep(1) c1.send('包子 %s' %i) c2.send('包子 %s' %i) producer()
生產器小結
1、生成器是可迭代物件
2、實現了延遲計算、省記憶體
3、生成器本質和其他的資料型別一樣,都是實現了迭代器協議,只不過生成器附加了一個延遲計算省記憶體的好處,其餘的可迭代物件可沒有這點好處
五、生成器表示式和列表解析
#1、三元表示式 name="alex" name="yangyl" res="1" if name=="yangyl" else "2" print(res) egg_list=["雞蛋%s" %i for i in range(10) ] #列表解析 print(egg_list) #使用生產器獲取 egg_two=("雞蛋%s" %i for i in range(10)) #生產器表示式 print(egg_two) print(egg_two.__next__()) print(next(egg_two)) #next()本質就是呼叫__next__
總結:
1、把列表解析中的[]換成() 得到的就是生成器表示式
2、列表解析與生成器表示式都是一種便利的程式設計方式,只不過生成器表示式更節省記憶體
3、python不但使用迭代器協議,讓for迴圈變得更加通用。大部分內建函式,也是使用迭代器協議訪問物件的。列如:sum函式是python的內建函式,該函式使用迭代器協議訪問物件,而生成器實現了迭代器協議,所以我們可以直接這樣計算一系列值的和:
s1=sum(x ** 2 for x in range(4)) print(s1)
而不用多此一舉先構造一個列表
s2=sum([x ** 2 for x in range(4)]) print(s2)