乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      厲害了,小編看過的介紹迭代器和生成器最易懂、最全面的文章

       heii2 2019-03-14

      作者:浪子燕青       鏈接:


      迭代器:是訪問數(shù)據(jù)集合內(nèi)元素的一種方式,一般用來遍歷數(shù)據(jù),但是他不能像列表一樣使用下標來獲取數(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ù),當然這里切記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ù)會暫停,并且保存當前的運行狀態(tài),返回返回當前的數(shù)值,并在下一次執(zhí)行next方法的時候,又從當前位置繼續(xù)往下走。

      簡單用法

      舉個例子:

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

      返回結(jié)果:

      <generator object gen at 0x000001487FDA2D58> 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é)果:

      <generator object gen at 0x000002885FE32D58> 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語言當中的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 (langzi)
      # 加載名字 為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é)碼,當函數(shù)存在yield的時候,python會將這個函數(shù)標記成生成器,當調(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ù)|符號分開,如何讀?。?/p>

      寫入文件代碼:

      # -*- 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)

      (完)

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多