Python之路(第十篇)迭代器協議、for循環機制、三元運算、列表解析式、生成器


一、迭代器協議

a迭代的含義

  迭代器即迭代的工具,那什么是迭代呢?
#迭代是一個重復的過程,每次重復即一次迭代,並且每次迭代的結果都是下一次迭代的初始值

b為何要有迭代器?

對於序列類型:字符串、列表、元組,我們可以使用索引的方式迭代取出其包含的元素。但對於字典、集合、文件等類型是沒有索引的,若還想取出其內部包含的元素,則必須找出一種不依賴於索引的迭代方式,這就是迭代器

 

c可迭代對象

可迭代對象指的是內置有iter方法的對象,即字符串、元組、列表、集合、字典、文件,

  
'hello'.__iter__
(1,2,3).__iter__
[1,2,3].__iter__
{'a':1}.__iter__
{'a','b'}.__iter__
open('a.txt').__iter__

d迭代器對象

  
可迭代對象執行obj.__iter__()得到的結果就是迭代器對象
而迭代器對象指的是即內置有__iter__又內置有__next__方法的對象

 

可迭代對象(字符串、元組、列表、集合、字典、文件)通過調用

  
__iter__()

方法,這里是遵循迭代器協議,將可迭代對象轉為一個迭代器,這時既可以調用

  
__iter__()方法又內置有__next__()方法

即為迭代器對象。迭代器對象是一個內存地址。

迭代器對象本身也可以使用__iter__()方法

迭代器對象再次使用__iter__()方法生成的還是迭代器對象。

例子

  
  dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
  iter_dic = dic.__iter__()
  print(iter_dic)
  v =iter_dic.__iter__()
  print(v)

  

輸出結果

  
  <dict_keyiterator object at 0x02191600>
  <dict_keyiterator object at 0x02191600>

  



分析:這里可以看到,對字典dic調用了__iter__()方法,使其變成迭代器對象,再次對這個迭代器對象使用__iter__()方法還是其本身。

 

e迭代器協議

 

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

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

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

f注意:

迭代器對象一定是可迭代對象,而可迭代對象不一定是迭代器對象。

 

例子

 

  s  = "hello"
  iter_s = s.__iter__() #將字符串用__iter__()方法轉換為迭代器對象
  print(iter_s.__next__())  #調用__next__()方法依次按照順序打印每個字符
  print(iter_s.__next__())
  print(iter_s.__next__())
  print(iter_s.__next__())
  print(iter_s.__next__())
  print(iter_s.__next__()) #拋出異常StopIteration,或者說結束標志

  


輸出結果

  
  h
  e
  l
  l
  o
  #拋出異常StopIteration,或者說結束標志,StopIteration

  

這里等同於用for循環打印

  
  s  = "hello"
  for i in s:  #for i in s.__iter__()
      print(i)  #print(iter_s.__next__())直到出現StopIteration,然后結束循環

  

分析:這里的for 循環里的for i in s,s調用了__iter__()方法,將s變為一個迭代器對象,同時對這個迭代器對象使用

__next__()方法打印出來,循環訪問,並處理了最后的StopIteration,結束了循環。

 

小知識

next()方法是調用python解釋器的,等同於某個可迭代對象下的__next__()方法

  
  #print(next(iter_s))等同於print(iter_s.__next__())

  

 

例子2

 

  
  dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
  iter_dic = dic.__iter__()
  print(iter_dic.__next__())
  print(iter_dic.__next__())
  print(iter_dic.__next__())
  print(iter_dic.__next__())
  # print(iter_dic.__next__())  產生StopIteration停止標志

  

輸出結果

  
  k1
  k2
  k3
  k4

  

改成for循環

  
  dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
  for i in dic:  #dic調用了__iter__方法,將其改成迭代器對象
      print(i)   #使用__next__()方法挨個去打印,直到出現StopIteration結束

  

 

用while循環實現

 

  
  dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
  iter_dic = dic.__iter__()
  while True:
      try:
          print(iter_dic.__next__())
      except StopIteration:
          print("迭代結束了,循環終止")
          break
          

  

輸出結果

  
  k1
  k2
  k3
  k4
  #迭代結束了,循環終止

  

 

g迭代器的優缺點

優點:

  • 提供一種統一的、不依賴於索引的迭代方式

  • 惰性計算,節省內存

缺點:

  • 無法獲取長度(只有在next完畢才知道到底有幾個值)

  • 一次性的,只能往后走,不能往前退

 

二、三元運算

三元表達式的格式

  
為真時的結果 if 判定條件 else 為假時的結果

如果條件成立,返回if前面的結果,否則else 返回else后的結果

例子

  
  a = 2
  b = 3
  s  = a if a < b else b   #這里的if語句后不加冒號
  print(s)

  

輸出結果

  
  2

  

例子2

 

  
  name = input('姓名>>: ')
  res = 'SB' if name == 'ken' else 'NB'
  print(name,res)
 

  

三、列表解析式

列表解析是Python迭代機制的一種應用,它常用於實現創建新的列表,返回的是一個列表,因此用在[]中。

例子

生成1-100以內的偶數

普通使用for循環的方式

  
  li  = []
  for i in range(1,101):
      if i % 2 == 0:
          li.append(i)
      else:
          pass
  print(li)

  

使用列表解析式

  
  li  = []
  res = [i for i in range(1,101)if i % 2 == 0 ]
  print(res)
 

  

 

例子2

將字符串變大寫組成列表

普通方式

  
  s  = "nicholas"
  li = []
  for i in s :
      res = i.upper()
      li.append(res)
  print(li)

  

列表解析式

  
  s  = "nicholas"
  li = [i.upper() for i in s ]
  print(li)

  

 

四、生成器

如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出后續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。

 

要創建一個generator,有很多種方法。

第一種方法很簡單,只要把一個列表生成式的[]改成(),就創建了一個generator,即生成器表達式:

生成器表達式能做的事情列表解析基本都能處理,只不過在需要處理的序列比較大時,列表解析比較費內存。

例子

  
  li = [i*i for i in range(10) ]
  print(li)

  

輸出結果

  
  [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

  

分析:這里是生成了一個0到9的平方的列表。

這里要改成生成器只要把列表生成式的[]改成()

  
  li = [i*i for i in range(10) ]
  g = (i*i for i in range(10) )
  print(li)
  print(g)

  

輸出結果

  
  [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  <generator object <genexpr> at 0x02221600>

  

分析:這里的<generator object <genexpr> at 0x02221600>就是一個生成器。這是生成器的第一種形式。

生成器是包含有__iter__()__next__()方法的,所以可以直接使用for來迭代

在這里調用__next__()方法或者用next()直接打印

 

 
        
  li = [i*i for i in range(5) ]
  g = (i*i for i in range(5) )
  print(li)
  print(g)
  print(g.__next__())
  print(g.__next__())
  print(g.__next__())
  print(next(g))
  print(next(g))
  #print(next(g))    #執行到此處就會產生一個StopIteration錯誤,類似可迭代對象調用__next__()一樣

  

輸出結果

  
  [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  <generator object <genexpr> at 0x02211600>
  0
  1
  4
  9
  16

  

這里的g生成器就是一個迭代器。

 

 

這里也可以用for循環直接打印

li = [i*i for i in range(5) ]
g = (i*i for i in range(5) )
print(li)
for i in g:
    print(i)

  

輸出結果

[0, 1, 4, 9, 16]
0
1
4
9
16

 

分析:同樣的這里的對生成器g的for循環自動處理了StopIteration錯誤,結束了for循環。與處理可迭代對象的方式類似。

 

第二種是生成器函數:

在函數中如果出現了yield關鍵字,那么該函數就不再是普通函數,而是生成器函數。生成器函數可以生產一個無線的序列,這樣列表根本沒有辦法進行處理。

yield 的作用就是把一個函數變成一個 generator,帶有 yield 的函數不再是一個普通函數,Python 解釋器會將其視為一個 generator。函數名+括號就變成了生成器。

 

例子

下面為一個可以生產奇數的生成器函數。

def num():
    n=1
    while True:
        print("函數內的第一處",n)
        yield n
        print("函數內奇數", n)
        n+=2
        print("函數內的第二處", n)
new_num = num()
print(new_num)
next(new_num)
print("函數外面的",next(new_num))

  

輸出結果

<generator object num at 0x02421660>
函數內的第一處 1
函數內奇數 1
函數內的第二處 3
函數內的第一處 3
函數外面的 3

  

分析:執行print(new_num)語句可以看到,這里的自定義函數是一個生成器,通過next()方法調用執行函數內部語句,這里的yield相當於return的功能,每次next()返回一個迭代值,下次next()繼續執行循環,於是函數繼續執行,直到再次遇到 yield,再次返回。看起來就好像一個函數在正常執行的過程中被 yield 中斷了數次,每次中斷都會通過 yield 返回當前的迭代值。而不是在while True語句下一次性執行完語句。

 

yield 與 return

return 是返回並中止函數

yield 是返回數據,並凍結當前的執行過程

next喚醒凍結的函數執行過程,繼續執行,直到遇到下一個yield

例子

def g():
   yield 1
   yield 2
   yield 3
new_g = g()
print(next(new_g)) 
#第一次調用next(new_g)時,會在執行完yield語句后掛起,所以此時程序並沒有執行結束,函數返回數據1,並凍結當前執行過程,等待下一個next()喚醒執行過程
print(next(new_g))
#通過next()喚醒執行過程,yield返回數據2,凍結執行過程,等待下一個next()
print(next(new_g))
# print(next(new_g))  
#這里如果運行上面這條語句,程序試圖從yield語句的下一條語句開始執行,發現已經到了結尾,所以拋出StopIteration異常。

  

輸出結果

1
2
3

  

分析:在一個生成器中,如果沒有return,則默認執行到函數完畢時返回StopIteration;

 

如果遇到return,如果在執行過程中 return,則直接拋出 StopIteration 終止迭代。

例子

def g():
   yield 1
   yield 2
   return "a"
   yield 3
new_g = g()
print(next(new_g))#通過next()執行函數,得到返回數據1,凍結當前過程,等待下一個next()喚醒
print(next(new_g))#通過next()執行函數,得到返回數據1,凍結當前過程,等待下一個next()喚醒
print(next(new_g))#通過next()執行函數,遇到return語句,直接拋出StopIteration 終止迭代,這樣yield '3'語句永遠也不會執行。
print(next(new_g))

  

輸出結果

1
Traceback (most recent call last):
2
 File "D:/exercise/test1.py", line 11, in <module>
    print(next(new_g))
StopIteration: a  #如果在return后返回一個值,那么這個值為StopIteration異常的說明,不是程序的返回值。

  

如果在return后返回一個值,那么這個值為StopIteration異常的說明,不是程序的返回值。

生成器沒有辦法使用return來返回值。

 

例子

與上面的例子基本相同,只是修改了return的返回值

def g():
   yield 1
   yield 2
   return "some"
   yield 3
new_g = g()
print(next(new_g))
print(next(new_g))
print(next(new_g))
print(next(new_g))
 
        

輸出結果

1
Traceback (most recent call last):
2
 File "D:/exercise/test1.py", line 11, in <module>
    print(next(new_g))
StopIteration: some    #生成器return返回的值是為StopIteration異常的說明,不是程序的返回值。

  

生成器支持的方法
close()

手動關閉生成器函數,后面的調用會直接返回StopIteration異常。

例子

def g():
   yield 1
   yield 2
   yield 3

new_g = g()
print(next(new_g))
print(next(new_g))
new_g.close()   #這里通過cloes()方法直接關閉了生成器,后續通過next()也無法喚醒生成器繼續執行返回數據,也就是說無法返回數據3,在這里直接拋出StopIteration異常
print(next(new_g))
print(next(new_g))

 

輸出結果

1
Traceback (most recent call last):
2
  File "D:/exercise/test1.py", line 11, in <module>
    print(next(new_g))
StopIteration

  

send()方法

生成器函數最大的特點是可以接受外部傳入的一個變量,並根據變量內容計算結果后返回。

 

例子

def gen():
      value = 0
      while True:
          receive = yield value
          if receive == "stop":
              break
          value = 'got: %s' % receive
  ​
  g=gen()
  print(g.send(None))
  print(g.send('aaa'))
  print(g.send(3))
  print(g.send('stop'))

  

輸出結果

  
  Traceback (most recent call last):
    File "D:/exercise/test6.py", line 18, in <module>
      print(g.send('stop'))
  StopIteration
  0
  got: aaa
  got: 3

  

分析:

執行過程:

1、首先g.send(None)或者g.next()喚醒生成器,並執行到receive = yield value語句,凍結執行過程,等下一個next()、g.send()、g.close(),此時,執行完了yield語句,但是沒有給receive賦值。

注意:在啟動生成器函數時只能send(None)或者next(g),如果試圖輸入其它的值都會得到錯誤提示信息。

2、通過g.send('aaa'),會傳入aaa,並賦值給receive,然后計算出value的值,並回到while頭部,執行yield value語句有停止。

此時yield value會輸出"got: aaa",然后凍結執行狀態。

3、通過g.send(3),會重復第2步,最后輸出結果為"got: 3"

4、通過g.send('stop'),傳入生成器函數,賦值給receive,執行if receive == "stop":break 退出循環,整個函數執行完畢,最后出現StopIteration異常

 

throw()

throw()用來向生成器函數送入一個異常,可以結束系統定義的異常,或者自定義的異常。throw()后直接拋出異常並結束程序,或者消耗掉一個yield,或者在沒有下一個yield的時候直接進行到程序的結尾。

 

例子

def gen():
      while True:
          try:
              yield 'normal value'
              yield 'normal value 2'
              print('here')
          except ValueError:
              print('we got ValueError here')
          except TypeError:
              break
  g=gen()
  print(next(g))
  print(g.throw(ValueError))
  print(next(g))
  print(next(g))
  print(next(g))
  print(g.throw(TypeError))

  

輸出結果

  normal value
  we got ValueError here
  normal value    
  normal value 2
  here
  normal value
  normal value 2
  Traceback (most recent call last):
   File "D:/exercise/test6.py", line 22, in <module>
   print(g.throw(TypeError))
  StopIteration

  

分析:

執行過程

1、通過print(next(g))喚醒生成器,執行到yield 'normal value',返回"normal value"被輸出,凍結生成器函數執行過程

2、執行print(g.throw(ValueError))語句,出現了ValueError,這里直接執行了except ValueError下的內容,循環繼續,返回try語句,執行了yield 'normal value',這里又輸出了一個“normal value”,之后凍結執行過程

3、執行了print(next(g)),函數里執行yield 'normal value 2'語句,輸出“normal value 2”,凍結執行過程,

4、執行了print(next(g)),函數里開始繼續執行,輸出“here”,之后調到開始執行yield 'normal value',輸出“normal value”,凍結執行過程

5、執行了print(next(g)),函數里執行yield 'normal value 2',輸出“normal value 2”,凍結執行過程

6、通過print(g.throw(TypeError)),跳出try語句,出現TypeError,直接執行except TypeError:

跳出while循環,到達生成器函數結尾,所以拋出StopIteration異常。

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM