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