python學習------迭代器協議和生成器

1、遞歸和迭代

遞歸:本身調用本身python

舉例解釋:問路   A問B康明網絡科技怎麼走,B說我不是很清楚,我幫你問問C,C說我也不知道。我問問D,D說 就在興隆。以後D返回結果給C,C返回結果給B,B返回結果給A.編程

迭代:迭,是更新的意思,每一次結果都是依據上一次結果產生。網絡

舉例解釋:問路   A問B康明網絡科技怎麼走,B說我不是很清楚,C可能知道你本身去問,C說我也不知道。D可能知道你本身去問,D說 就在興隆。app

 

 

2、什麼是迭代器協議
1.迭代器協議是指:對象必須提供一個next方法,執行該方法要麼返回迭代中的下一項,要麼就引發一個
stoplteration異常,以終止迭代(只能日後走不能往前退)
2.可迭代對象:實現了迭代器協議的對象(如何實現:對象內部定義一個__iter__()方法)
3.協議是一種約定,可迭代對象實現了迭代器協議,python的內部工具(如for循環,sum,min,max函數等)
使用迭代器協議訪問對象。

3、python中強大的for循環機制
for循環的本質:循環全部對象,全都是使用迭代器協議。
正本清源:
不少人會想,for循環的本質就是遵循迭代器協議去訪問對象,那麼for循環的對象
確定都是迭代器了啊,沒錯,那既然這樣,for循環能夠遍歷(字符串,列表,元組,字典,集合
,文件對象),那這些類型的數據確定都是可迭代對象啊?可是,爲何定義一個列表
沒有next()方法?

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

而後for循環調用可迭代對象的__next__方法去取值,並且for循環會捕捉stoplteration異常,
以終止迭代
l = ['a','b','c']
#一:下標訪問方式 print(l[0])
print(l[1])
print(l[2])
print(l[3])    #超出邊界報錯:IndexError

#二:遵循迭代器協議訪問方式
diedai_l = l.__iter__()
print(diedai_l.__next__())
print(diedai_l.__next__())
print(diedai_l.__next__())
#print(diedai_l.__next__())  #超出邊界報錯:StopIteration

#三:for循環訪問方式
for循環l本質就是遵循迭代器協議的訪問方式,先調用diedai_l=l.__iter__()方法,
或者直接diedai_l=iter(l),而後依次執行diedai_l.next(),直到for循環捕捉到stopIteration
終止循環
#for循環全部對象的本質都是同樣的原理

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

#四:用while去模擬for循環作的事情
diedai_l = l.__iter__()

while True:
    try:
        print(diedai_l.__next__())
    except StopIteration:
        print('迭代完畢了,循環終止了')
        break

 

4、爲什麼要有for循環
基於上面講的列表三種訪問方式,聰明的你立馬看出了端倪,因而你不知死活大聲喊道,你這不逗我玩呢麼,
有了下標的訪問方式,我能夠這樣遍歷一個列表啊

l=[1,2,3]

index = 0
while index < len(l):
    print(l[index])
    index+=1
#要毛線for循環  要毛線for循環  要毛線for循環

沒錯,序列類型字符串,列表,元組都有下標,你用上述的方式訪問,perfect!
可是你可曾想過非序列類型像字典,集合,文件對象的感覺,因此,for循環就是基於迭代器
協議提供的一個統一的能夠遍歷全部對象的方法,即在遍歷以前,先調用對象的
__iter__方法將其轉換成一個迭代器,而後使用迭代器協議去實現循環訪問,這樣
全部對象就均可以經過for循環來遍歷了,並且你看到的結果也確實如此,這就是
無所不能的for循環,覺悟吧,年輕人
 
l =['a','b','c','d']
diedai_l = l.__iter__()
print(diedai_l.__next__())
print(next(diedai_l))
#next()等同於__next__ ,只不過next()是系統內置函數

 

 
5、生成器初探
什麼是生成器?
能夠理解爲一種數據類型,這種數據類型自動實現了迭代器協議(其餘的數據類型須要調用本身內置的
__iter__方法),因此生成器就是可迭代對象。

生成器分類及在python中的表現形式:(python有兩種不一樣的方式提供生成器)
1.生成器函數常規函數定義,可是,使用yield語句而不是return語句返回結果。yield語句一次返回
一個結果,在每一個結果中間,過去函數的狀態,以便下次重它理想的地方繼續執行
def test():
    yield 1
    yield 2
    yield 3
g = test()
print(g)
print(g.__next__())
print(g.__next__())

#輸出結果:
<generator object test at 0x0000000001E0D2A0>
1
2
 

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


爲什麼使用生成器之生成器的優勢
python使用生成器對延遲操做提供了支持。所謂延遲操做,是指在
須要的時候才產生結果,而不是當即產生結果。這也是生成器的主要好處

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

 6、生成器函數

def lay_eggs(num):
    egg_list=[]
    for egg in range(num):
        egg_list.append('蛋%s' %egg)
    return egg_list
yikuangdan = lay_eggs(10) #咱們拿到的是蛋
print(yikuangdan)
#缺點1.佔用空間大    2.運行效率低

def lay_eggs(num): for egg in range(num): yield '蛋%s' %egg print('下完一個蛋') laomuji =lay_eggs(10) #咱們拿到的是一隻母雞 (生成器) print(laomuji) print(laomuji.__next__()) print(laomuji.__next__()) print(laomuji.__next__()) egg_l =list(laomuji) print(egg_l) #演示只能日後不能往前 #演示蛋下完了,母雞就死了

 

7、生成器表達式和列表解析

#三元表達式
name ='alex'
#name = 'zhu.mr'
res ='shuai' if name == 'alex' else 'no'
print(res)
#alex送雞蛋報恩情
egg_list = ['雞蛋%s' %i for i in range(10)]   #列表解析

#對方回絕了。說你送雞蛋不如送我個母雞吧,回頭我在家裏本身下蛋

laomuji = ('雞蛋%s' %i for i in range(10)) #生成器表達式
print(laomuji)
print(next(laomuji))
print(laomuji.__next__())
print(next(laomuji))

總結:
1.把列表解析的[]換成()獲得的就是生成器表達式
2.列表解析與生成器表達式都是一種遍歷的編程方式,只不過生成器表達式更節省內存。
3.python不但使用迭代器協議讓for循環變得更加通用。大部份內置函數,也是使用迭代器協議
訪問對象的。例如,sum()函數是python的內置函數,該函數使用迭代器協議訪問對象
,而生產器實現了迭代器協議,因此咱們能夠直接這樣計算一系列的和:
sum(x ** 2 for x in range(4))
而不是畫蛇添足的先構造一個列表
sum( [x ** 2 for x in range(4)]  )

 

8、生成器總結
綜上已經對生成器有了必定的認識,下面咱們以生成器函數爲例進行總結
a.語法上和函數相似:生成器函數和常規函數幾乎是同樣的。它們都是使用def語句進行定義,差異在於,生成器使用yield語句返回一個值,而
常規函數使用return語句返回一個值
b.自動實現迭代器協議:對於生成器,python會自動實現迭代器協議,以便應用到迭代背景中(如for循環,sum函數)。因爲生成器自動實現迭代器協議,
因此,咱們能夠調用他的next方法,而且,在沒有值能夠返回的時候,生成器自動產生stoplteration異常。
c.狀態掛起:生成器使用yield語句返回一個值。yield語句掛起該生成器函數的狀態,保留足夠的信息,以便以後從它離開的地方繼續執行。

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

#列表解析
sum([i for i in range(1000000)]) #內存佔用大,機器容易卡死

#生成器表達式
sum(i for i in range(1000000)) #幾乎不佔內存


優勢二:生成器還能有效提升代碼可讀性
#求一段文字中,每一個單子出現的位置
def index_words(text):
    result=[]
    if text:
        result.append(0)
    for index, letter in enumerate(text,1):
        if letter == ' ':
            result.append(index)
    return result
print(index_words('hello alex da db'))

 

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

使用生成器之後,代碼行數更少。你們要記住,若是想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數越少越好
不使用生成器的時候,對於每次結果,咱們首先看到的是result.append(index),其次,纔是index。也就是說,咱們每次看到的是一個列表的append操做,只是append的是咱們想要的結果。使用生成器的時候,直接yield index,少了列表append操做的干擾,咱們一眼就可以看出,代碼是要返回index。
這個例子充分說明了,合理使用生成器,可以有效提升代碼可讀性。只要你們徹底接受了生成器的概念,理解了yield語句和return語句同樣,也是返回一個值。那麼,就可以理解爲何使用生成器比不使用生成器要好,可以理解使用生成器真的可讓代碼變得清晰易懂。

注意事項:生成器只能遍歷一次(母雞一輩子只能下必定數量的蛋,下完了就死掉了)
#人口信息.txt文件內容
{'name':'北京','population':10}
{'name':'南京','population':100000}
{'name':'山東','population':10000}
{'name':'山西','population':19999}

def get_provice_population(filename):
    with open(filename) as f:
        for line in f:
            p=eval(line)
            yield p['population']
gen=get_provice_population('人口信息.txt')

all_population=sum(gen)
for p in gen:
    print(p/all_population)

執行上面這段代碼,將不會有任何輸出,這是由於,生成器只能遍歷一次。在咱們執行sum語句的時候,就遍歷了咱們的生成器,當咱們再次遍歷咱們的生成器的時候,將不會有任何記錄。因此,上面的代碼不會有任何輸出。函數

所以,生成器的惟一注意事項就是:生成器只能遍歷一次。工具

相關文章
相關標籤/搜索