Python基礎(四)——迭代器/物件,生成器

孔鬍子發表於2019-05-17

  首先廖雪峰網站寫的內容就我目前初步學習來說,已經相當詳實,知識點平鋪直敘讓人易接受,所以以下內容均作為一種摘記記錄以及補充

1. 列表生成器

  主要目的是建立 list 。多看例子就能清楚:

print(list(range(1,10,2)))  #[1, 3, 5, 7, 9].生成1~9(左閉右開),相隔為2
print([t * t for t in range(1,10,3) if t % 2 == 0]) #[16].生成1~9相隔4,且是偶數的平方和
print([m + n for m in '123' for n in '456']) #['14', '15', '16', '24', '25', '26', '34', '35', '36'].全排列

d = {'x': 'A', 'y': 'B', 'z': 'C'}
for k, v in d.items():
    print(k, '=', v)

print([m + '=' + n for m,n in d.items()])  #['x=A', 'y=B', 'z=C']

#篩選單詞,並全變小寫
L = ['Hello', 'World', 18, 'Apple', None]
print([t.lower() for t in L if isinstance(t, str)])  #['hello', 'world', 'apple']

 

2. Iterable vs Iterator

  iterable 是可迭代物件,iterator 是迭代器。兩者都是 collection.abc 中得抽象類。iterator 繼承自 iterable 。

  •   iterable 有常見得 list,dict,str,tuple 等或者自定義的類(該類必須實現抽象方法 _iter()_)。當一個可迭代物件作為引數呼叫自身的 iter() 方法時,會返回一個迭代器。迭代器擁有 _next()_ 抽象方法,可迭代物件沒有,通過該方法就可以逐個得到 “序列” 中的各個值,不斷呼叫 _next()_ 方法,最後會引起 StopIteration 異常報錯,代表迭代結束了。同時迭代器還擁有 _iter()_ 方法,所以迭代器也是個可迭代物件。即所有的迭代器都是可迭代物件,但是可迭代物件並不都是迭代器,基本判斷方法是是否呼叫 next() 方法,list,dect,str,tuple 都並不行,即不是迭代器。

     我們可以通過 isinstance 來判斷:

from collections.abc import Iterable,Iterator
t = [1,2,3]   #列表
print(isinstance(t, Iterable)) #true
print(isinstance(t, Iterator)) #false

         我們常用的 for...in [ ] 。就是利用了迭代器

from collections.abc import Iterable,Iterator
L = [1,2,3]
print(isinstance(L, Iterator)) #Flase
for t in L:
    print(t, end=' ') #1  2  3

          這是我們常寫的程式碼,輸出123。既然 L 不是迭代器為啥也能迭代輸出呢。這就是在使用 for...in 的時候,Python 直譯器主動將可迭代物件呼叫了 iter() 返回迭代器,即每次都是通過迭代器的 next() 方法進行輸出。那麼哪個異常 StopIteration 呢?異常應該被 for...in 內部處理了,並不顯式的丟擲。

     我們換一種更明顯的寫法:

1 from collections.abc import Iterable,Iterator
2 L = [1,2,3]
3 print(isinstance(L, Iterator))  #false
4 T = L.__iter__()
5 print(isinstance(T, Iterator))  #true。現在T就是迭代器了,擁有了next()方法。
6 print(T.__next__())  # 1
7 print(T.__next__())  # 2
8 print(T.__next__())  # 3
9 print(T.__next__())  # StopIteration

         結果和我們想的是一樣的。或者再這樣寫

 1 from collections.abc import Iterable,Iterator
 2 L = [1,2,3]
 3 print(isinstance(L, Iterator))  #false
 4 T = iter(L)
 5 while True:
 6     try:
 7         print(next(T), end=' ')
 8     except StopIteration:
 9         print('結束')
10         break

  (點選圖片檢視原文)

 

3. 生成器和 yield

  • 生成器是返回一個 generator iterator 的函式。但是這個函式中包含 yield 表示式,除此之外別無它異,用來產生一系列供 for 迴圈使用的值或者通過 next() 逐一獲取。所以生成器一般也稱為生成器函式。
  • 生成迭代器 generator iterator 是由生成器 generator 建立的物件。每遇到 yield 會暫停(相當於return),並記住當前位置,之後在繼續在記住的位置繼續向下執行。而不同於普通函式每次都由上往下執行。
  • 第一種建立生成器的方法:將列表生成式的 [ ] 換成 ( )
L = ( t * t for t in range(1,10))
print(L)  #<generator object <genexpr> at 0x0000028D2F68B840>
print(next(L)) # 1
print(next(L)) # 4
for i in L: # 迭代輸出
    print(i)

 

 

 

  • 當一個生成函式被呼叫時,返回一個迭代器,成為生成器。這個生成器來控制生成函式的執行,遇到 yield 就掛起,下次繼續從 掛起處執行。前面說過迭代器有 next() 方法,所以這裡的yield 就是幹了 next() 方法的事。一樣不斷next 直到無資料 StopIteration。
  • 第二種是通過定義函式
 1 def test():
 2     print('1')
 3     yield
 4     print('2')
 5     yield
 6     print('3')
 7     yield
 8 
 9 t = test()  # t 是生成器,生成器來控制函式
10 print(t) # <generator object test at 0x0000021EF6C5B840>
11 next(t) # 1
12 next(t) # 2
13 next(t) # 3
14 next(t) # StopIteration

 

  可以通過 11~14 行看出,yield 起的作用就是掛起。第一次呼叫next() 方法,函式執行到第三句就停了,第二次呼叫 next() 執行到第五句。yield 就像是 OS 中的中斷語句,保護現場--恢復現場。

  再來看一個斐波那契例子:

 1 #斐波那契數列
 2 #常規寫法一:
 3 # def fib(max):
 4 #     n, a, b = 0, 0, 1
 5 #     while n < max:
 6 #         print(b)
 7 #         a, b = b, a + b
 8 #         n = n + 1
 9 #     return 'done'
10 #
11 # fib(6)
12 
13 #生成器寫法二:
14 from collections.abc import Iterator,Iterable
15 def fib(max):
16     n, a, b = 0, 0, 1
17     while n < max:
18         yield b     # yield 類似於return 將 b 返回
19         a, b = b, a + b
20         n = n + 1
21     return 'done'
22 
23 f = fib(6)
24 print(f)  #<generator object fib at 0x00000124DDD8B840>
25 print(isinstance(f, Iterator)) #True。生成函式返回迭代器
26 
27 for n in f:
28     print(n, end=' ')  #1 1 2 3 5 8

  通過觀察24,25行可以知道,呼叫了 fib() 之後,函式並沒有執行到尾(否則返回 str = 'done'),正如上文所說,返回的是一個 生成器,也就是呼叫生成函式(含 yield語句的)返回生成器,然後我們通過生成器來控制函式的執行。只有執行27行的 for...in 的時候,才會去執行15~21這段函式程式碼。

  具體執行過程:第一次從16至18行停止,因為yield的存在,執行到18行就停了,然後返回一個值 b 給 for 迴圈,然後執行28行輸出 b,然後next()迭代器繼續從上次停止的地方的下一行19行繼續執行(迭代器next()只要不是StopIteration 或者生成函式結束了,for 迴圈就得以繼續),然後在while迴圈內,再次執行到18行停止,返回 b 給 for。繼續重複,直至跳出while迴圈,fib() 這段生成器函式結束了,for...in 也就結束了。

 1 # 生成器寫法三
 2 from collections.abc import Iterator,Iterable
 3 def fib(max):
 4     n, a, b = 0, 0, 1
 5     while n < max:
 6         yield b     # yield 類似於return 將 b 返回
 7         a, b = b, a + b
 8         n = n + 1
 9 
10 f = fib(3)
11 print(f)  #<generator object >
12 print(isinstance(f, Iterator)) #True。生成函式返回迭代器
13 print(next(f))  # 1
14 print(next(f))  # 1
15 print(next(f))  # 2
16 print(next(f))  # StopIteration

 

 

4.生成器send()方法

  stackoverflow 上還有關於生成 yield 配合使用 send()的方法。查閱官網,send(value) 函式意思:恢復執行,並向生成器傳送一個值,value 引數將被當作 yield 表示式結果。

1 def test():
2     while True:
3         x = yield
4         yield x * 2
5 
6 g = test()
7 print(next(g)) # none
8 print(g.send(12)) #24

   我們已經知道 yield 可以當作return來理解。

  首先第六行建立了 g (生成器),第七行輸出 none,因為執行第七行,也就是去執行test()函式了,函式順利執行到第三行,3 = yield 明顯是我們學的賦值語句,難道是將yield賦值給3?不是的。先解釋輸出的none,因為沒有引數寫在yield的右邊,即沒有引數返回,所以第七行輸出 None。同時因為yield存在而停止繼續。

  而第八行:遇到g.send() 會繼續執行上次執行到第三行的地方,這裡傳入的引數12就是賦值給x的。所以再往下第四行,yiled看成return 返回12*2,同時test()函式被掛起,返回24給第八行。至此函式結束。

  又比如:

1 def test(x):
2     while True:
3         x *= 2
4         x = yield x
5 
6 g = test(3)
7 print(next(g)) # 6
8 print(g.send(12))  # 24

  第四行意思:先看右邊yield x 就是返回 x 。再看左邊 x = yield 就是賦值給 x 。所以不難理解了。不解釋了。 

 

5. 補充(點選圖片檢視原文)

 

相關文章