<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 9053字,需瀏覽 19分鐘

           ·

          2021-01-26 15:32

          作者:浪子燕青

          原文:

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

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

          迭代器生成器是Python很重要的進階語法。雖然在一開始的學(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進去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()進行轉(zhuǎn)換

          6. Python的for循環(huán)本質(zhì)上是使用next()進行不斷調(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)建一個默認的迭代器,該迭代器調(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方法并沒有索引值,所以需要自己維護一個索引值,方便獲取下一個變量的位置。

          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
          ????????#?索引值+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進階知識/迭代協(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
          ????????#?維護索引值
          ????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讓類變成迭代器(要維護索引值)。

          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
          ????????#?維護索引值
          ????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)。

          新建一個迭代器,用迭代器維護索引值,返回根據(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)用滿足“后進先出”的原則,也就是說,最后被調(diào)用的函數(shù)應(yīng)該第一個返回,函數(shù)的遞歸調(diào)用就是一個經(jīng)典的例子。顯然,內(nèi)存中以“后進先出”方式處理數(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í)行時遵循的先進后出順序。因為這些特性,生成器不僅能用于生成可迭代對象,還可以用于實現(xiàn)多任務(wù)協(xié)作。

          就是說只要拿到了這個生成器對象,就能對這個生成器對象進行控制,比如繼續(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
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人精品一区二区三区 | 国产一级精品成人无码毛片 | 舔逼av| 啊啊啊额在线视频 | 亚洲AV高清 |