1. 程式人生 > >python 第五章 叠代器,生成器,生成器函數

python 第五章 叠代器,生成器,生成器函數

第五章 獲取 內部 return語句 原理 叠代 exc 字符串 int

叠代器

叠代器是訪問集合元素的一種方式。叠代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。叠代器只能往前不會後退,不過這也沒什麽,因為人們很少在叠代途中往後退。另外,叠代器的一大優點是不要求事先準備好整個叠代過程中所有的元素。叠代器僅僅在叠代到某個元素時才計算該元素,而在這之前或之後,元素可以不存在或者被銷毀。這個特點使得它特別適合用於遍歷一些巨大的或是無限的集合,比如幾個G的文件

特點:
訪問者不需要關心叠代器內部的結構,僅需通過next()方法不斷去取下一個內容
不能隨機訪問集合中的某個值 ,只能從頭到尾依次訪問
訪問到一半時不能往回退
便於循環比較大的數據集合,節省內存

 1 #
!/usr/bin/env python 2 # -*- coding:utf8 -*- 3 4 l = [1,2,3] 5 iter = l.__iter__() #遵叠代器協議,生成可叠代對象 6 print(iter.__next__()) 7 print(iter.__next__()) 8 print(iter.__next__()) 9 #輸出 10 1 11 2 12 3

for會自動將列表,字符串,字典,集合,元祖,文件,通過__iter__ 生成可叠代對象
然後通過__next__方法取值

 1 #!/usr/bin/env python
 2 # -*- coding:utf8 -*-
3 4 """ for循環內部自動通過__iter__ 生成可叠代對象然後通過__next__方法取值 """ 5 l = [1,2,3] 6 for i in l: 7 print(i) 8 #輸出 9 1 10 2 11 3

生成器
通過列表生成式,我們可以直接創建一個列表,但是,受到內存限制。列表容量肯定是有限的。而且,創建一個包含100萬個元素
的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,哪後面絕大多數元素占用的空間就都白白浪費了、
所以,如果列表元素可以按照某種算法推算出來, 哪我們是否可以在循環的過程中不斷推算出後續的元素呢?這樣就不必創建完整的list,從而節省大量的空間,在python中這種循環一邊計算的機制,稱為生成器 generator
要創建一個 generator 有很多種方法,第一種方法很簡單,只要把一個列表生成式的[]改成(),就創建了一個 generator

 1 #!/usr/bin/env python
 2 # -*- coding:utf8 -*-
 3 
 4 L = [x*x for x in range(10)]
 5 print(L)
 6 #輸出
 7 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
 8 
 9 g = (x*x for x in range(10))  #生成器 generator
10 print(g)
11 #輸出
12 <generator object <genexpr> at 0x000000000218D408>

創建L 和g 的區別在於最外層的[]和(),L是一個list g是一個 generator
我們可以直接打印出list的每一個元素,哪怎麽打印出 generator的每一個元素
如果要一個一個打印出來 可以通過 next()函數獲得 generator的下一個返回值;

 1 #!/usr/bin/env python
 2 # -*- coding:utf8 -*-
 3 
 4 """generator保存的是算法,每次調用next(g),就計算出g的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出錯誤"""
 5 
 6 g = (x*x for x in range(3))
 7 print(next(g))
 8 #輸出
 9 0
10 
11 print(next(g))
12 #輸出
13 1
14 
15 print(next(g))
16 #輸出
17 4
18 
19 print(next(g)) #報錯

當然上面這種不斷調用next(g)實在是太變態了,正確的方法是使用for循環,因為generator也是可叠代對象

 1 #!/usr/bin/env python
 2 # -*- coding:utf8 -*-
 3 
 4 """通過for循環"""
 5 
 6 g = (x*x for x in range(3))
 7 for n in g:
 8     print(n)
 9 #輸出
10 0
11 1
12 4

所以我們創建了
一個generator後,基本上永遠不會調用next() 而是通過for循環來叠代它,並且不需要關心 stopiteration錯誤
generator非常強大,如果推算的算法比較復雜,用類似列表生成式的for循環無法實現的時候,還可以用函數來實現
比如斐波拉契數列 除第一個數和第二個數外,任意一個數都可以由前兩個數相加得到:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

 1 #!/usr/bin/env python
 2 # -*- coding:utf8 -*-
 3 
 4 """斐波拉契數列用列表生成式寫不出來,但是,用函數把它打印出來卻很容易"""
 5 
 6 def fib(max):
 7    n,a,b = 0,0,1
 8    while n < max:
 9        print(b)
10        a,b = b,a+b
11        n = n+1
12    return "done"
13 fib(10)
14 #輸出
15 1
16 1
17 2
18 3
19 5
20 8
21 13
22 21
23 34
24 55

仔細觀察可以看出,fib函數實際上是定義了斐波拉契數列的推算規則,可以從第一個元素開始,推算出後續任意的元素,這種邏輯其實非常類似 generator也就是說上面的函數和generator 僅一步之遙,要把函數變成generator,只需要吧 print(b)改為 yield b 就可以了

 1 #!/usr/bin/env python
 2 # -*- coding:utf8 -*-
 3 
 4 def fid(max):
 5     n, a, b = 0, 0 ,1
 6     while n < max:
 7         yield b
 8         a,b =b, a+b
 9         n = n + 1
10     return "done"
11 print(list(fid(10)))
12 #輸出
13 [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

這就是定義generator的另一種方式。

生成器函數
yield相當於return控制的是函數的返回值
yield的另一個特性,接受send傳過來的值,賦值給x
send() 同next 但是括號裏可以輸入一個值 傳給yield
vield 相當於return 但是可以使用多次

 1 #!/usr/bin/env python
 2 # -*- coding:utf8 -*-
 3 
 4 def test():
 5     yield 1
 6     yield 2
 7     yield 3
 8 g = test()
 9 print(g)
10 #輸出
11 <generator object test at 0x000000000277D408>
12 
13 print(next(g))
14 #輸出
15 1
16 
17 print(next(g))
18 #輸出
19 2
20 
21 print(next(g))
22 #輸出
23 3

如果一個函數定義中 包含yield 關鍵字,那麽這個函數就是一個generator
這裏最難理解的就是 generator和函數執行流程不一樣,函數是順序執行,遇到return語句或者最後一行函數語句就返回,而變成generator的函數,在每次調用 next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行

 1 #!/usr/bin/env python
 2 # -*- coding:utf8 -*-
 3  """舉一個簡單的例子 ,定義一個 generator ,依次返回數字 1, 3, 5"""
 4 
 5 def odd():
 6     print("step 1")
 7     yield 1
 8     print("step 2")
 9     yield (3)
10     print("steo 3")
11     yield (5)
12 print(odd())
13 #輸出
14 <generator object odd at 0x000000000211D408>
15 
16 """調用該generator時,首先要生成一個generator對象,然後用next()函數不斷獲得下一個返回值:"""
17 i = odd()
18 next(i)
19 #輸出
20 step 1
21 1
22 
23 next(i)
24 #輸出
25 step 2
26 3
27 
28 next(i)
29 #輸出
30 step 3
31 5
32 next(i)
33 #輸出
34 報錯

可以看到,odd不是普通的函數,而是generator,在執行過程中,遇到 yield就中斷,下次又繼
續執行。執行三次yield後 已經沒有yield 可以執行了,所以,第4次調用next(o)就報錯同樣的,把函數改成 generator後,我們基本上從來不會用next()來獲取 下一個返回值,而是直接使用for循環來叠代;但是用for循環調用generator時,發現拿不到generator的return語句的返回值,如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:

 1 #!/usr/bin/env python
 2 # -*- coding:utf8 -*-
 3 
 4 def fid(max):
 5     n, a, b = 0, 0 ,1
 6     while n < max:
 7         yield b
 8         a,b =b, a+b
 9         n = n + 1
10     return "done"
11 g = fid(6)
12 while True:
13     try:
14         x = next(g)
15         print(g,x)
16     except StopIteration as e:
17         print("Generator  return valur:", e.value)
18         break
19 
20 #輸出
21 g 1
22 g 1
23 g 2
24 g 3
25 g 5
26 g 8
27 Generator  return valur: done

要理解 generator 的工作原理,它是在for循環的過程中不斷計算出下一個元素,並在適當的條件
結束for循環,對於函數改成generator 來說 遇到return語句或者執行到函數體最後一行語句,就是結束generator、的指令 for循環隨之結束。
請註意區分普通函數和generator函數,普通函數調用直接放回結果
generator函數調用實際返回一個generator對象

python 第五章 叠代器,生成器,生成器函數