1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        深入淺出地解讀Python迭代器和生成器

        共 9053字,需瀏覽 19分鐘

         ·

        2021-01-26 15:32

        作者:浪子燕青

        原文:

        http://www.langzi.fun/迭代器與生成器.html

        大家好,歡迎來到 Crossin的編程教室 !

        迭代器生成器是Python很重要的進(jìn)階語法。雖然在一開始的學(xué)習(xí)中,不理解它們并不影響你寫代碼。但到一定階段之后,如果沒有掌握其原理,你可能無法徹底理解代碼的運行邏輯。今天我們就給大家分享一篇關(guān)于迭代器和生成器的深度解讀。

        迭代器與可迭代對象

        概念

        迭代器:是訪問數(shù)據(jù)集合內(nèi)元素的一種方式,一般用來遍歷數(shù)據(jù),但是他不能像列表一樣使用下標(biāo)來獲取數(shù)據(jù),也就是說迭代器是不能返回的。

        1. Iterator:迭代器對象,必須要實現(xiàn)next魔法函數(shù)

        2. Iterable:可迭代對象,繼承Iterator,必須要實現(xiàn)iter魔法函數(shù)

        比如:

        from?collections?import?Iterable,Iterator
        a?=?[1,2,3]
        print(isinstance(a,Iterator))
        print(isinstance(a,Iterable))

        返回結(jié)果:

        False
        True

        在Pycharm中使用alt+b進(jìn)去list的源碼中可以看到,在list類中有iter魔法函數(shù),也就是說只要實現(xiàn)了iter魔法函數(shù),那么這個對象就是可迭代對象。

        上面的例子中a是一個列表,也是一個可迭代對象,那么如何才能讓這個a變成迭代器呢?使用iter()即可。

        from?collections?import?Iterable,Iterator
        a?=?[1,2,3]
        a?=?iter(a)
        print(isinstance(a,Iterator))
        print(isinstance(a,Iterable))
        print(next(a))
        print('----')
        for?x?in?a:
        ????print(x)

        返回結(jié)果:

        True
        True
        1
        ----
        2
        3

        可以看到現(xiàn)在a是可迭代對象又是一個迭代器,說明列表a中有iter方法,該方法返回的是迭代器,這個時候使用next就可以獲取a的下一個值,但是要記住迭代器中的數(shù)值只能被獲取一次。

        梳理迭代器(Iterator)與可迭代對象(Iterable)的區(qū)別:

        1. 可迭代對象:繼承迭代器對象,可以用for循環(huán)(說明實現(xiàn)了iter方法)

        2. 迭代器對象:可以用next獲取下一個值(說明實現(xiàn)了next方法),但是每個值只能獲取一次,單純的迭代器沒有實現(xiàn)iter魔法函數(shù),所以不能使用for循環(huán)

        3. 只要可以用作for循環(huán)的都是可迭代對象

        4. 只要可以用next()函數(shù)的都是迭代器對象

        5. 列表,字典,字符串是可迭代對象但是不是迭代器對象,如果想變成迭代器對象可以使用iter()進(jìn)行轉(zhuǎn)換

        6. Python的for循環(huán)本質(zhì)上是使用next()進(jìn)行不斷調(diào)用,for循環(huán)的是可迭代對象,可迭代對象中有iter魔法函數(shù),可迭代對象繼承迭代器對象,迭代器對象中有next魔法函數(shù)

        7. 一般由可迭代對象變迭代器對象

        可迭代對象

        可迭代對象每次使用for循環(huán)一個數(shù)組的時候,本質(zhì)上會從類中嘗試調(diào)用iter魔法函數(shù),如果類中有iter魔法函數(shù)的話,會優(yōu)先調(diào)用iter魔法函數(shù),當(dāng)然這里切記iter方法必須要返回一個可以迭代的對象,不然就會報錯。

        如果沒有定義iter魔法函數(shù)的話,會創(chuàng)建一個默認(rèn)的迭代器,該迭代器調(diào)用getitem魔法函數(shù),如果你沒有定義iter和getitem兩個魔法函數(shù)的話,該類型就不是可迭代對象,就會報錯。

        比如:

        class?s:
        ????def?__init__(self,x):
        ????????self.x?=?x
        ????def?__iter__(self):
        ????????return?iter(self.x)
        ????????#?這里必須要返回一個可以迭代的對象
        ????#?def?__getitem__(self,?item):
        ????#?????return?self.x[item]
        #?iter和getitem其中必須要實現(xiàn)一個
        a?=?s('123')
        #?這里的a就是可迭代對象
        #?這里不能調(diào)用next(a)方法,因為沒有定義
        for?x?in?a:
        ????print(x)

        這里把注釋符去掉返回結(jié)果也是一樣的,返回結(jié)果:

        1
        2
        3

        迭代器對象

        一開始提起,iter搭配Iterable做可迭代對象,next搭配Iterator做迭代器。next()接受一個迭代器對象,作用是獲取迭代器對象的下一個值,迭代器是用來做迭代的,只會在需要的時候產(chǎn)生數(shù)據(jù)。

        和可迭代對象不同,可迭代對象一開始是把所有的列表放在一個變量中,然后用getitem方法不斷的返回數(shù)值,getitem中的item就是索引值。

        但是next方法并沒有索引值,所以需要自己維護(hù)一個索引值,方便獲取下一個變量的位置。

        class?s:
        ????def?__init__(self,x):
        ????????self.x?=?x
        ????????#?獲取傳入的對象
        ????????self.index?=?0
        ????????#?維護(hù)索引值
        ????def?__next__(self):
        ????????try:
        ????????????result?=?self.x[self.index]
        ????????????#?獲取傳入對象的值
        ????????except?IndexError:
        ????????????#?如果索引值錯誤
        ????????????raise?StopIteration
        ????????#?拋出停止迭代
        ????????self.index?+=?1
        ????????#?索引值+1,用來獲取傳入對象的下一個值
        ????????return?result
        ????????#?返回傳入對象的值

        a?=?s([1,2,3])
        print(next(a))
        print('----------')
        for?x?in?a:
        #?類中并沒有iter或者getitem魔法函數(shù),不能用for循環(huán),會報錯
        ????print(x)

        返回結(jié)果:

        Traceback?(most?recent?call?last):
        1
        ----------
        ??File?"C:/CODE/Python進(jìn)階知識/迭代協(xié)議/迭代器.py",?line?34,?in?<module>
        ????for?x?in?a:
        TypeError:?'s'?object?is?not?iterable

        上面一個就是完整的迭代器對象,他是根據(jù)自身的索引值來獲取傳入對象的下一個值,并不是像可迭代對象直接把傳入對象讀取到內(nèi)存中,所以對于一些很大的文件讀取的時候,可以一行一行的讀取內(nèi)容,而不是把文件的所有內(nèi)容讀取到內(nèi)存中。

        這個類是迭代器對象,那么如何才能讓他能夠使用for循環(huán)呢?那就讓他變成可迭代對象,只需要在類中加上iter魔法函數(shù)即可。

        class?s:
        ????def?__init__(self,x):
        ????????self.x?=?x
        ????????#?獲取傳入的對象
        ????????self.index?=?0
        ????????#?維護(hù)索引值
        ????def?__next__(self):
        ????????try:
        ????????????result?=?self.x[self.index]
        ????????????#?獲取傳入對象的值
        ????????except?IndexError:
        ????????????#?如果索引值錯誤
        ????????????raise?StopIteration
        ????????#?拋出停止迭代
        ????????self.index?+=?1
        ????????#?索引值+1,用來獲取傳入對象的下一個值
        ????????return?result
        ????????#?返回傳入對象的值
        ????def?__iter__(self):
        ????????return?self
        a?=?s([1,2,3])
        print(next(a))
        print('----------')
        for?x?in?a:
        ????print(x)

        返回結(jié)果:

        1
        ----------
        2
        3

        可以看到這個時候運行成功,但是這個對象還是屬于迭代器對象,因為在next獲取下一個值會報錯。

        知識整理

        根據(jù)上面的代碼提示,得到規(guī)律:

        1. iter讓類變成可迭代對象,next讓類變成迭代器(要維護(hù)索引值)。

        2. 可迭代對象可以用for循環(huán),迭代器可以用next獲取下一個值。

        3. 迭代器如果想要變成可迭代對象用for循環(huán),就要在迭代器內(nèi)部加上iter魔法函數(shù)

        4. 可迭代對象如果想要能用next魔法函數(shù),使用自身類中的iter()方法即可變成迭代器對象

        代碼演示:

        class?s:
        ????def?__init__(self,x):
        ????????self.x?=?x
        ????????self.index?=?0
        ????def?__next__(self):
        ????????try:
        ????????????result?=?self.x[self.index]
        ????????except?IndexError:
        ????????????raise?StopIteration
        ????????self.index?+=?1
        ????????return?result

        class?b:
        ????def?__init__(self,x):
        ????????self.x?=?x
        ????def?__iter__(self):
        ????????return?s(self.x)
        a?=?b([1,2,3])

        for?x?in?a:
        ????print(x)

        返回結(jié)果:

        1
        2
        3

        這個時候是不能再用next方法了,應(yīng)為類b是一個可迭代對象,并非迭代器,這個時候不能用next方法,但是可以讓類b繼承類s,這樣就能用next()方法獲取下一個值,但是你的類b中要存在索引值,不然會報錯,如下代碼:

        class?s:
        ????def?__init__(self,x):
        ????????self.x?=?x
        ????????#?獲取傳入的對象
        ????????self.index?=?0
        ????????#?維護(hù)索引值
        ????def?__next__(self):
        ????????try:
        ????????????result?=?self.x[self.index]
        ????????????#?獲取傳入對象的值
        ????????except?IndexError:
        ????????????#?如果索引值錯誤
        ????????????raise?StopIteration
        ????????#?拋出停止迭代
        ????????self.index?+=?1
        ????????#?索引值+1,用來獲取傳入對象的下一個值
        ????????return?result
        ????????#?返回傳入對象的值
        ????#?def?__iter__(self):
        ????#?????return?self
        class?b(s):
        ????def?__init__(self,x):
        ????????self.x?=?x
        ????????self.index?=?0
        ????def?__iter__(self):
        ????????return?s(self.x)
        a?=?b([1,2,3])

        print(next(a))
        print(next(a))

        返回結(jié)果:

        1
        2

        可以這么做,但是沒必要,因為這樣違反了設(shè)計原則。

        迭代器的設(shè)計模式

        迭代器模式:提供一種方法順序訪問一個聚合對象中的各種元素,而又不暴露該對象的內(nèi)部
        表示。

        迭代器的設(shè)計模式是一種經(jīng)典的設(shè)計模式,根據(jù)迭代器的特性(根據(jù)索引值讀取下一個內(nèi)容,不一次性讀取大量數(shù)據(jù)到內(nèi)存)不建議將next和iter都寫在一個類中去實現(xiàn)。

        新建一個迭代器,用迭代器維護(hù)索引值,返回根據(jù)索引值獲取對象的數(shù)值,新建另一個可迭代對象,使用iter方法方便的循環(huán)迭代器的返回值。

        生成器

        生成器:函數(shù)中只要有yield,這個函數(shù)就會變成生成器。每次運行到y(tǒng)ield的時候,函數(shù)會暫停,并且保存當(dāng)前的運行狀態(tài),返回返回當(dāng)前的數(shù)值,并在下一次執(zhí)行next方法的時候,又從當(dāng)前位置繼續(xù)往下走。概念

        簡單用法

        舉個例子:

        def?gen():
        ????yield?1
        ????#?返回一個對象,這個對象的值是1
        def?ret():
        ????return?1
        ????#?返回一個數(shù)字1
        g?=?gen()
        r?=?ret()
        print(g,r)
        print(next(g))

        返回結(jié)果:

        ?1
        1

        可以看到return是直接返回數(shù)值1,yield是返回的一個生成器對象,這個對象的值是1,使用next(g)或者for x in g:print x 都是可以獲取到他的內(nèi)容的,這個對象是在python編譯字節(jié)碼的時候就產(chǎn)生。

        def?gen():
        ????yield?1
        ????yield?11
        ????yield?111
        ????yield?1111
        ????yield?11111
        ????yield?111111
        ????#?返回一個對象,這個對象內(nèi)的值是1和11,111...
        def?ret():
        ????return?1
        ????return?3
        ????#?第二個return是無效的
        g?=?gen()
        r?=?ret()
        print(g,r)
        print(next(g))
        for?x?in?g:
        ????print(x)

        返回結(jié)果:

        ?1
        1
        11
        111
        1111
        11111
        111111

        就像迭代器的特性一樣,獲取過一遍的值是沒法再獲取一次的,并且不是那種一次把所有的結(jié)果求出放在內(nèi)存或者說不是一次性讀取所有的內(nèi)容放在內(nèi)存中。

        梳理特性:

        1. 使用yield的函數(shù)都是生成器函數(shù)

        2. 可以使用for循環(huán)獲取值,也可以使用next獲取生成器函數(shù)的值

        原理

        函數(shù)工作原理:函數(shù)的調(diào)用滿足“后進(jìn)先出”的原則,也就是說,最后被調(diào)用的函數(shù)應(yīng)該第一個返回,函數(shù)的遞歸調(diào)用就是一個經(jīng)典的例子。顯然,內(nèi)存中以“后進(jìn)先出”方式處理數(shù)據(jù)的棧段是最適合用于實現(xiàn)函數(shù)調(diào)用的載體,在編譯型程序語言中,函數(shù)被調(diào)用后,函數(shù)的參數(shù),返回地址,寄存器值等數(shù)據(jù)會被壓入棧,待函數(shù)體執(zhí)行完畢,將上述數(shù)據(jù)彈出棧。這也意味著,一個被調(diào)用的函數(shù)一旦執(zhí)行完畢,它的生命周期就結(jié)束了。

        python解釋器運行的時候,會用C語言當(dāng)中的PyEval_EvalFramEx函數(shù)創(chuàng)建一個棧幀,所有的棧幀都是分配再堆內(nèi)存上,如果不主動釋放就會一直在里面。

        Python 的堆棧幀是分配在堆內(nèi)存中的,理解這一點非常重要!Python 解釋器是個普通的 C 程序,所以它的堆棧幀就是普通的堆棧。但是它操作的 Python 堆棧幀是在堆上的。除了其他驚喜之外,這意味著 Python 的堆棧幀可以在它的調(diào)用之外存活。(FIXME: 可以在它調(diào)用結(jié)束后存活),這個就是生成器的核心原理實現(xiàn)。

        Python腳本都會被python.exe編譯成字節(jié)碼的形式,然后python.exe再執(zhí)行這些字節(jié)碼,使用dis即可查看函數(shù)對象的字節(jié)碼對象。

        import?dis
        #?查看函數(shù)程序字節(jié)碼
        a?=?'langzi'
        print(dis.dis(a))
        print('-'*20)
        def?sb(admin):
        ????print(admin)
        print(dis.dis(sb))

        返回結(jié)果:

        ??1???????????0?LOAD_NAME????????????????0?(lzngzi)
        #?加載名字?為langzi
        ??????????????2?RETURN_VALUE
        #?返回值
        None
        --------------------
        ?15???????????0?LOAD_GLOBAL??????????????0?(print)
        #?加載一個print函數(shù)
        ??????????????2?LOAD_FAST????????????????0?(admin)
        #?加載傳遞參數(shù)為admin
        ??????????????4?CALL_FUNCTION????????????1
        #?調(diào)用這個函數(shù)
        ??????????????6?POP_TOP
        #?從棧的頂端把元素移除出來
        ??????????????8?LOAD_CONST???????????????0?(None)
        #?因為該函數(shù)沒有返回任何值,所以加載的值是none
        ?????????????10?RETURN_VALUE
        #?最后把load_const的值返回(個人理解)
        None

        代碼函數(shù)運行的時候,python將代碼編譯成字節(jié)碼,當(dāng)函數(shù)存在yield的時候,python會將這個函數(shù)標(biāo)記成生成器,當(dāng)調(diào)用這個函數(shù)的時候,會返回生成器對象,調(diào)用這個生成器對象后C語言中寫的函數(shù)會記錄上次代碼執(zhí)行到的位置和變量。

        再C語言中的PyGenObject中有兩個值,gi_frame(存儲上次代碼執(zhí)行到的位置f_lasti的上次代碼執(zhí)行到的變量f_locals),gi_code(存儲代碼),使用dis也可以獲取到上次代碼執(zhí)行的位置和值。

        舉個例子:

        import?dis
        def?gen():
        ????yield?1
        ????yield?2
        ????return?666

        g?=?gen()
        #?g是生成器對象
        print(dis.dis(g))
        print('*'*10)
        print(g.gi_frame.f_lasti)
        #?這里還沒有執(zhí)行,返回的位置是-1
        print(g.gi_frame.f_locals)
        #?這里還沒有執(zhí)行,返回的對象是{}
        next(g)
        print('*'*10)
        print(g.gi_frame.f_lasti)
        print(g.gi_frame.f_locals)

        返回結(jié)果:

        ?11???????????0?LOAD_CONST???????????????1?(1)
        #?加載值為1
        ??????????????2?YIELD_VALUE
        ??????????????4?POP_TOP

        ?12???????????6?LOAD_CONST???????????????2?(2)
        ??????????????8?YIELD_VALUE
        ?????????????10?POP_TOP

        ?13??????????12?LOAD_CONST???????????????3?(666)
        ?????????????14?RETURN_VALUE
        None
        **********
        -1
        #?因為還沒有執(zhí)行,所以獲取的行數(shù)為?-1
        {}
        **********
        2
        #?這里開始執(zhí)行了第一次,獲取的行數(shù)是2,2對應(yīng)2?YIELD_VALUE就是前面加載的數(shù)值1
        {}
        # g.gi_frame.f_locals 是局部變量,你都沒定義那么獲取的結(jié)果自然是{},你只需在代碼中加上user='admin',這里的{}就會改變。

        生成器可以在任何時候被任何函數(shù)恢復(fù)執(zhí)行,因為它的棧幀實際上不在棧上而是在堆上。生成器在調(diào)用調(diào)用層次結(jié)構(gòu)中的位置不是固定的,也不需要遵循常規(guī)函數(shù)執(zhí)行時遵循的先進(jìn)后出順序。因為這些特性,生成器不僅能用于生成可迭代對象,還可以用于實現(xiàn)多任務(wù)協(xié)作。

        就是說只要拿到了這個生成器對象,就能對這個生成器對象進(jìn)行控制,比如繼續(xù)執(zhí)行暫停等待,這個就是協(xié)程能夠執(zhí)行的理論原理。

        應(yīng)用場景

        讀取文件,使用open('xxx').read(2019)//打開一個文件,每次讀取2019個偏移量。文件a.txt是一行文字,但是特別長,這一行文字根據(jù)|符號分開,如何讀???

        寫入文件代碼:

        #?-*-?coding:utf-8?-*-
        import?random
        import?threading
        import?string
        import?time
        t1?=?time.time()
        def?write(x):
        ????with?open('a.txt','a+')as?a:
        ????????a.write(x?+?'||')

        def?run():
        ????for?x?in?range(10000000):
        ????????strs?=?str(random.randint(1000,2000))?+random.choice(string.ascii_letters)*10
        ????????write(strs)
        for?x?in?range(10):
        ????t?=?threading.Thread(target=run)
        ????t.start()
        t2?=?time.time()
        print(t2?-?t1)

        讀取文件代碼:

        #?-*-?coding:utf-8?-*-
        def?readbooks(f,?newline):
        ????#?f為傳入的文件名,newline為分隔符
        ????buf?=?""
        ????#?緩存,處理已經(jīng)讀出來的數(shù)據(jù)量
        ????while?1:
        ????????while?newline?in?buf:
        ????????????#?緩存中的數(shù)據(jù)是否存在分隔符
        ????????????pos?=?buf.index(newline)
        ????????????#?如果存在就找到字符的位置,比如0或者1或者2
        ????????????yield?buf[:pos]
        ????????????#?暫停函數(shù),返回緩存中的從頭到字符的位置
        ????????????buf?=?buf[pos?+?len(newline):]
        ????????????#?緩存變成了,字符的位置到末尾
        ????????chunk?=?f.read(2010?*?10)
        ????????#?讀取2010*10的字符
        ????????if?not?chunk:
        ????????????#?已經(jīng)讀取到了文件結(jié)尾
        ????????????yield?buf
        ????????????break
        ????????buf?+=?chunk
        ????????#?加到緩存
        ?with?open('a.txt','r')as?f:
        ?????for?line?in?readbooks(f,'||'):
        ?????????print(line)


        作者:安全研發(fā)
        來源:安全研發(fā)



        _往期文章推薦_

        可迭代對象和迭代器




        瀏覽 42
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            麻豆一区二区 | 娇妻被黑人各种姿势玩 | 少妇高潮喷水视频 | 国产高清视频 | 亚洲欧美久久久 | www.毛片| 97资源站人妻 | 青青色在线观看 | 亚洲精品久久久久 | 欧美一级高清片 |