1. 程式人生 > >詳解生成器、叠代器

詳解生成器、叠代器

生成器、叠代器

1.叠代  

  要搞清楚什麽關於叠代器,生成器,可叠代對象,前提是我們要理解何為叠代。

  第一,叠代需要重復進行某一操作

  第二,本次叠代的要依賴上一次的結果繼續往下做,如果中途有任何停頓,都不能算是叠代.

  下面來看看幾個例子,你就會更能理解叠代的含義。

技術分享

# example1
# 非叠代count = 0while count < 10:    print("hello world")
    count += 1

技術分享

技術分享

# example2
# 叠代count = 0while count < 10:    print(count)
    count += 1

技術分享

  例子1,僅僅只是在重復一件事,那就是不停的打印"hello world",並且,這個打印的結果並不依賴上一次輸出的值。而例子2,就很好地說明叠代的含義,重復+繼續。

2.可叠代對象

  按照上面叠代的含義,我們應該能夠知道何為可叠代對象。顧名思義,就是一個對象能夠被叠代的使用。那麽我們該如何判斷一個對象是否可叠代呢?

  Python提供了模塊collections,其中有一個isinstance(obj,string)的函數,可以判斷一個對象是否為可叠代對象。看下面實例:

技術分享

from collections import Iterable

f = open(‘a.txt‘)
i = 1s = ‘1234‘d = {‘abc‘:1}
t = (1,2,344)
m = {1,2,34,}print(isinstance(i, Iterable))  # 判斷整型是否為可叠代對象print(isinstance(s, Iterable))  # 判斷字符串對象是否為可叠代對象  print(isinstance(d, Iterable))  # 判斷字典對象是否為可叠代對象print(isinstance(t, Iterable))  # 判斷元組對象是否為可叠代對象print(isinstance(m, Iterable))  # 判斷集合對象是否為可叠代對象print(isinstance(f, Iterable))  # 判斷文件對象是否為可叠代對象########輸出結果#########False
True
True
True
True
True

技術分享

  由上面得出,除了整型之外,python內的基本數據類型都是可叠代對象,包括文件對象。那麽,python內部是如何知道一個對象是否為可叠代對象呢?答案是,在每一種數據類型對象中,都會有有一個__iter__()方法,正是因為這個方法,才使得這些基本數據類型變為可叠代。

  如果不信,我們可以來看看下面代碼片段:

技術分享檢測屬性方法

  如果大家還是不信,可以繼續來測試。我們自己來寫一個類,看看有__iter__()方法和沒有此方法的區別。

技術分享View Code

  從上面,實驗結果可以看出一個對象是否可叠代,關鍵看這個對象是否有__iter__()方法。

3.叠代器

  在介紹叠代器之前,我們先來了解一下容器這個概念。

  容器是一種把多個元素組織在一起的數據結構,容器中的元素可以逐個地叠代獲取。簡單來說,就好比一個盒子,我們可以往裏面存放數據,也可以從裏面一個一個地取出數據。

  在python中,屬於容器類型地有:list,dict,set,str,tuple.....。容器僅僅只是用來存放數據的,我們平常看到的 l = [1,2,3,4]等等,好像我們可以直接從列表這個容器中取出元素,但事實上容器並不提供這種能力,而是可叠代對象賦予了容器這種能力。

  說完了容器,我們在來談談叠代器。叠代器與可叠代對象區別在於:__next__()方法。

  我們可以采用以下方法來驗證一下:

技術分享View Code

  結果顯示:除了文件對象為叠代器,其余均不是叠代器

  下面,我們進一步來驗證一下:

技術分享View Code

  從輸出結果可以表明,叠代器與可叠代對象僅僅就是__next__()方法的有無。

4.for內部機制剖析

  先來看看一段普通的叠代過程:

l = [1,2,3,4,5]for i in l:    print(i)

  根據之前的分析,我們知道 l = [1,2,3,4,5]是一個可叠代對象。而且可叠代對象是不可以直接從其中取到元素。那麽為啥我們還能從列表L中取到元素呢?這一切都是因為for循環內部實現。在for循環內部,首先L會調用__iter__()方法,將列表L變為一個叠代器,然後這個叠代器再調用其__next__()方法,返回取到的第一個值,這個元素就被賦值給了i,接著就打印輸出了。

  下面,我們通過一系列的實驗來證明上述所說的。

技術分享View Code

  上述實驗,與我上面說明的一致。  

  下面,我們可以while循環來模擬for循環,輸出列表中的元素。

技術分享

l = [1,2,3,4,5]

item = l.__iter__()  # 生成一個叠代器while True:    try:
        i = item.__next__()        print(i)    except StopIteration:  # 捕獲異常,如果有異常,說明應該停止叠代
        break

技術分享

  由上分析,我們可以總結出:當我們試圖用for循環來叠代一個可叠代對象時候,for循環在內部進行了兩步操作:第一,將可叠代對象S變為叠代器M;第二,叠代器M調用__next__()方法,並且返回其取出的元素給變量i。

  技術分享

  你可能看見過這種寫法,for i in iter(M):xxx ,其實這一步操作和我們上面沒什麽區別。iter()函數,就是將一個可叠代對象M變為叠代器也就是M調用__iter__()方法,然後內部在調用__next__()方法。也就是說,

技術分享

M = [1,2,3,4,5]for i in iter(M):  # 等價於 M.__iter()__   人為顯示調用
    print(i)for i in M:  # 解釋器隱式調用
    print(i)##################
#上面輸出的結果完全一樣#
#################

技術分享

  還有next(M)等價於M.__next__。  

  叠代器優點:

    1.節約內存

    2.不依賴索引取值

    3.實現惰性計算(什麽時候需要,在取值出來計算)

5.生成器(本質就是叠代器)

  什麽是生成器?可以理解為一種數據類型,這種數據類型自動實現了叠代器協議(其他的數據類型需要調用自己內置的__iter__方法)。

  按照我們之前所說的,叠代器必須滿足兩個條件:既有__iter__(),又有__next__()方法。那麽生成器是否也有這兩個方法呢?答案是,YES。具體來通過以下代碼來看看。

技術分享

技術分享

def func():    print("one------------->")    yield 1    print("two------------->")    yield 2    print("three----------->")    yield 3    print("four------------>")    yield 4print(hasattr(func(),‘__next__‘))print(hasattr(func(),‘__iter__‘))#########輸出結果###########True
True

技術分享

  實驗表明,生成器就是叠代器。

  Python有兩種不同的方式提供生成器:

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

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

  既然生成器就是叠代器,那麽我們是不是也可以通過for循環來遍歷出生成器中的內容呢?看下面代碼.

技術分享

技術分享

def func():    print("one------------->")    yield 1    print("two------------->")    yield 2    print("three----------->")    yield 3    print("four------------>")    yield 4for i in func():    print(i)#########輸出結果########one------------->
1two------------->
2three----------->
3four------------>
4

技術分享

  很顯然,生成器也可以通過for循環來遍歷出其中的內容。

  下面我們來看看生成器函數執行流程:

技術分享View Code

  每次調用g.__next__()就回去函數內部找yield關鍵字,如果找得到就輸出yield後面的值並且返回;如果沒有找到,就會報出異常。上述代碼中如果在調用g.__next__()就會報錯。

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

1實例:生成器模擬Linux下tail -f a.txt | grep ‘error‘ | grep ‘404‘

技術分享

技術分享

import timedef tail(filepath):
    with open(filepath, encoding=‘utf-8‘) as f:
        f.seek(0, 2)  # 停到末尾開頭 1從當前位置  2從文件末尾
        while True:
            line = f.readline()            if line:  # 如果有內容讀出
                #print(line,end=‘‘)
                yield line  # 遍歷時停在此行,並且將其返回值傳遞出去
            else:
                time.sleep(0.5)  # 如果文件為空,休眠 等待輸入def grep(lines, patterns):  # lines為生成器類型
    for line in lines:  # 遍歷生成器
        if patterns in line:            yield line


g = grep(tail(‘a.txt‘), ‘error‘)  # 動態跟蹤文件新添加的內容,並且過濾出有patterns的行g1 = grep(g,‘404‘)  # g1為生成器for i in g1: # 通過for循環來隱式調用__next__()方法
    print(i)

技術分享

  生成器小結:

    1.是可叠代對象

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

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

6.可叠代對象、叠代器、生成器關系總結

技術分享


詳解生成器、叠代器