迭代器 生成器

一 什麼是迭代器協議

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

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

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

二 python中強大的for循環機制

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

正本清源:函數

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

 

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

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

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

 

Python的Iterator對象表示的是一個數據流,Iterator對象能夠被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。能夠把這個數據流看作是一個有序序列,但咱們卻不能提早知道序列的長度,只能不斷經過next()函數實現按需計算下一個數據,因此Iterator的計算是惰性的,只有在須要返回下一個數據時它纔會計算。spa

Iterator甚至能夠表示一個無限大的數據流,例如全體天然數。而使用list是永遠不可能存儲全體天然數的。code

小結

凡是可做用於for循環的對象都是Iterable類型;

凡是可做用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;

集合數據類型如listdictstr等是Iterable但不是Iterator,不過能夠經過iter()函數得到一個Iterator對象。

Python的for循環本質上就是經過不斷調用next()函數實現的。

 

三 生成器

經過列表生成式,咱們能夠直接建立一個列表。可是,受到內存限制,列表容量確定是有限的。並且,建立一個包含100萬個元素的列表,不只佔用很大的存儲空間,若是咱們僅僅須要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。

因此,若是列表元素能夠按照某種算法推算出來,那咱們是否能夠在循環的過程當中不斷推算出後續的元素呢?這樣就沒必要建立完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱爲生成器:generator。

要建立一個generator,有不少種方法。第一種方法很簡單,只要把一個列表生成式的[]改爲(),就建立了一個generator

什麼是生成器?

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

 

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

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

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

 

爲什麼使用生成器之生成器的優勢

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

 

生成器小結:

1.是可迭代對象

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

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

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)


def lay_eggs(num):
    for egg in range(num):
        res='蛋%s' %egg
        yield res
        print('下完一個蛋')

laomuji=lay_eggs(10)#咱們拿到的是一隻母雞
print(laomuji)
print(laomuji.__next__())
print(laomuji.__next__())
print(laomuji.__next__())
egg_l=list(laomuji)
print(egg_l)
#演示只能日後不能往前
#演示蛋下完了,母雞就死了

母雞下蛋的傳說
yield

 

 

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

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

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

1 #列表解析
2 sum([i for i in range(100000000)])#內存佔用大,機器容易卡死
3 
4 #生成器表達式
5 sum(i for i in range(100000000))#幾乎不佔內存

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

#求一段文字中,每一個單詞出現的位置
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 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__())#報錯

使用迭代器

 

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

  1. 使用生成器之後,代碼行數更少。你們要記住,若是想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數越少越好
  2. 不使用生成器的時候,對於每次結果,咱們首先看到的是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語句的時候,就遍歷了咱們的生成器,當咱們再次遍歷咱們的生成器的時候,將不會有任何記錄。因此,上面的代碼不會有任何輸出。

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

人口信息
View Code
相關文章
相關標籤/搜索