第八篇 迭代器協議和生成器

閱讀目錄

一 遞歸和迭代

二 什麼是迭代器協議

三 python中強大的for循環機制

四 爲何要有for循環

五 生成器初探

六 生成器函數

七 生成器表達式和列表解析

八 生成器總結

 

一 遞歸和迭代



 

二 什麼是迭代器協議


1.迭代器協議是指:對象必須提供一個next方法,執行該方法要麼返回迭代中的下一項,要麼就引起一個StopIteration異常,以終止迭代 (只能往後走不能往前退)


2.可迭代對象:實現了迭代器協議的對象(如何實現:對象內部定義一個__iter__()方法)


3.協議是一種約定,可迭代對象實現了迭代器協議,python的內部工具(如for循環,sum,min,max函數等)使用迭代器協議訪問對象。


 

三 python中強大的for循環機制


for循環的本質:循環所有對象,全都是使用迭代器協議。


正本清源:


很多人會想,for循環的本質就是遵循迭代器協議去訪問對象,那麼for循環的對象肯定都是迭代器了啊,沒錯,那既然這樣,for循環可以遍歷(字符串,列表,元組,字典,集合,文件對象),那這些類型的數據肯定都是可迭代對象啊?但是,我他媽的爲什麼定義一個列表l=[1,2,3,4]沒有l.next()方法,打臉麼。


 


(字符串,列表,元組,字典,集合,文件對象)這些都不是可迭代對象,只不過在for循環式,調用了他們內部的__iter__方法,把他們變成了可迭代對象


然後for循環調用可迭代對象的__next__方法去取值,而且for循環會捕捉StopIteration異常,以終止迭代


 

 1 l=['a','b','c']

 2 #一:下標訪問方式

 3 print(l[0])

 4 print(l[1])

 5 print(l[2])

 6 # print(l[3])#超出邊界報錯:IndexError

 7 

 8 #二:遵循迭代器協議訪問方式

 9 diedai_l=l.__iter__()

10 print(diedai_l.__next__())

11 print(diedai_l.__next__())

12 print(diedai_l.__next__())

13 # print(diedai_l.__next__())#超出邊界報錯:StopIteration

14 

15 #三:for循環訪問方式

16 #for循環l本質就是遵循迭代器協議的訪問方式,先調用diedai_l=l.__iter__()方法,或者直接diedai_l=iter(l),然後依次執行diedai_l.next(),直到for循環捕捉到StopIteration終止循環

  #for循環所有對象的本質都是一樣的原理

17 

18 for i in l:#diedai_l=l.__iter__()

19     print(i) #i=diedai_l.next()

20 

21 #四:用while去模擬for循環做的事情

22 diedai_l=l.__iter__()

23 while True:

24     try:

25         print(diedai_l.__next__())

26     except StopIteration:

27         print('迭代完畢了,循環終止了')

28         break

 

 

四 爲何要有for循環


基於上面講的列表的三種訪問方式,聰明的你立馬看除了端倪,於是你不知死活大聲喊道,你這不逗我玩呢麼,有了下標的訪問方式,我可以這樣遍歷一個列表啊


 

l=[1,2,3]


index=0

while index < len(l):

    print(l[index])

    index+=1


#要毛線for循環,要毛線for循環,要毛線for循環

 

 


沒錯,序列類型字符串,列表,元組都有下標,你用上述的方式訪問,perfect!但是你可曾想過非序列類型像字典,集合,文件對象的感受,所以嘛,年輕人,for循環就是基於迭代器協議提供了一個統一的可以遍歷所有對象的方法,即在遍歷之前,先調用對象的__iter__方法將其轉換成一個迭代器,然後使用迭代器協議去實現循環訪問,這樣所有的對象就都可以通過for循環來遍歷了,而且你看到的效果也確實如此,這就是無所不能的for循環,覺悟吧,年輕人


 

五 生成器初探


什麼是生成器?


可以理解爲一種數據類型,這種數據類型自動實現了迭代器協議(其他的數據類型需要調用自己內置的__iter__方法),所以生成器就是可迭代對象


 


生成器分類及在python中的表現形式:(Python有兩種不同的方式提供生成器)


1.生成器函數:常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行


2.生成器表達式:類似於列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表


 


爲何使用生成器之生成器的優點


Python使用生成器對延遲操作提供了支持。所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果。這也是生成器的主要好處。


 


生成器小結:


1.是可迭代對象


2.實現了延遲計算,省內存啊


3.生成器本質和其他的數據類型一樣,都是實現了迭代器協議,只不過生成器附加了一個延遲計算省內存的好處,其餘的可迭代對象可沒有這點好處,記住嘍!!!


 

六 生成器函數

 

 

 

七 生成器表達式和列表解析




 

 

總結:


1.把列表解析的[]換成()得到的就是生成器表達式


2.列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存


3.Python不但使用迭代器協議,讓for循環變得更加通用。大部分內置函數,也是使用迭代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用迭代器協議訪問對象,而生成器實現了迭代器協議,所以,我們可以直接這樣計算一系列值的和:


1 sum(x ** 2 for x in xrange(4))

而不用多此一舉的先構造一個列表:


1 sum([x ** 2 for x in xrange(4)]) 

 

八 生成器總結


綜上已經對生成器有了一定的認識,下面我們以生成器函數爲例進行總結


語法上和函數類似:生成器函數和常規函數幾乎是一樣的。它們都是使用def語句進行定義,差別在於,生成器使用yield語句返回一個值,而常規函數使用return語句返回一個值

自動實現迭代器協議:對於生成器,Python會自動實現迭代器協議,以便應用到迭代背景中(如for循環,sum函數)。由於生成器自動實現了迭代器協議,所以,我們可以調用它的next方法,並且,在沒有值可以返回的時候,生成器自動產生StopIteration異常

狀態掛起:生成器使用yield語句返回一個值。yield語句掛起該生成器函數的狀態,保留足夠的信息,以便之後從它離開的地方繼續執行

優點一:生成器的好處是延遲計算,一次返回一個結果。也就是說,它不會一次生成所有的結果,這對於大數據量處理,將會非常有用。


1 #列表解析

2 sum([i for i in range(100000000)])#內存佔用大,機器容易卡死

4 #生成器表達式

5 sum(i for i in range(100000000))#幾乎不佔內存

優點二:生成器還能有效提高代碼可讀性



 

 1 #求一段文字中,每個單詞出現的位置

 2 def index_words(text):

 3     result = []

 4     if text:

 5         result.append(0)

 6     for index, letter in enumerate(text, 1):

 7         if letter == ' ':

 8             result.append(index)

 9     return result

10 

11 print(index_words('hello alex da sb'))

 


 

#求一段文字中每個單詞出現的位置

def index_words(text):

    if text:

        yield 0

    for index, letter in enumerate(text, 1):

        if letter == ' ':

            yield index


g=index_words('hello alex da sb')

print(g)

print(g.__next__())

print(g.__next__())

print(g.__next__())

print(g.__next__())

print(g.__next__())#報錯

 

 

這裏,至少有兩個充分的理由說明 ,使用生成器比不使用生成器代碼更加清晰:


使用生成器以後,代碼行數更少。大家要記住,如果想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數越少越好

不使用生成器的時候,對於每次結果,我們首先看到的是result.append(index),其次,纔是index。也就是說,我們每次看到的是一個列表的append操作,只是append的是我們想要的結果。使用生成器的時候,直接yield index,少了列表append操作的干擾,我們一眼就能夠看出,代碼是要返回index。

這個例子充分說明了,合理使用生成器,能夠有效提高代碼可讀性。只要大家完全接受了生成器的概念,理解了yield語句和return語句一樣,也是返回一個值。那麼,就能夠理解爲什麼使用生成器比不使用生成器要好,能夠理解使用生成器真的可以讓代碼變得清晰易懂。


注意事項:生成器只能遍歷一次(母雞一生只能下一定數量的蛋,下完了就死掉了)


 

 1 人口信息.txt文件內容

 2 {'name':'北京','population':10}

 3 {'name':'南京','population':100000}

 4 {'name':'山東','population':10000}

 5 {'name':'山西','population':19999}

 6 

 7 def get_provice_population(filename):

 8     with open(filename) as f:

 9         for line in f:

10             p=eval(line)

11             yield p['population']

12 gen=get_provice_population('人口信息.txt')

13 

14 all_population=sum(gen)

15 for p in gen:

16     print(p/all_population)

17 執行上面這段代碼,將不會有任何輸出,這是因爲,生成器只能遍歷一次。在我們執行sum語句的時候,就遍歷了我們的生成器,當我們再次遍歷我們的生成器的時候,將不會有任何記錄。所以,上面的代碼不會有任何輸出。

18 

19 因此,生成器的唯一注意事項就是:生成器只能遍歷一次。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章