14-Python-生成器
1、生成器概念
通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素占用的空間都白白浪費了。
所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出後續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。
要創建一個generator,有很多種方法。第一種方法很簡單,只要把一個列表生成式的[]
改成()
,就創建了一個generator:
1>>> b = (i**2 for i in range(10)) 2 >>> b 3 <generator object <genexpr> at 0x0000022E4EFD8780> 4 >>> for n in b: #用for循環打印所有元素 5 print(n) 6 0 7 1 8 4 9 9 10 16 11 25 12 36 13 49 14 64 15 81
如果要一個一個打印出來,可以通過__next__()
函數獲得generator的下一個返回值:
1 >>> n = (i**2 fori in range(10)) 2 >>> print(n.__next__()) 3 0 4 >>> print(n.__next__()) 5 1 6 >>> print(n.__next__()) 7 4 8 >>> print(n.__next__()) 9 9 10 >>> print(n.__next__()) 11 16 12 >>> print(n.__next__()) 13 25 14 >>> print(n.__next__()) 1536 16 >>> print(n.__next__()) 17 49 18 >>> print(n.__next__()) 19 64 20 >>> print(n.__next__()) 21 81 22 >>> print(n.__next__()) #當沒有更多元素時,會拋出異常 23 Traceback (most recent call last): 24 File "<pyshell#34>", line 1, in <module> 25 print(n.__next__()) 26 StopIteration
2、生成器的其它姿勢
generator非常強大。如果推算的算法比較復雜,用類似列表生成式的for
循環無法實現的時候,還可以用函數來實現。
比如,著名的斐波拉契數列(Fibonacci),除第一個和第二個數外,任意一個數都可由前兩個數相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契數列用列表生成式寫不出來,但是,用函數把它打印出來卻很容易:
1 def fib(num): 2 n, a, b = 0, 0, 1 3 while n < num: 4 print(b) 5 a, b = b, a+b 6 n += 1 7 return "done" # 異常的時候打印該消息 8 ‘‘‘ 9 註意賦值語句 a, b = b, a+b,相當於如下 10 tmp = (b, a+b) #tmp為一個元組 11 a = tmp[0] 12 b = tmp[1] 13 ‘‘‘
然後執行fib()便可以打印出前N個數。仔細觀察,可以看出,fib
函數實際上是定義了斐波拉契數列的推算規則,可以從第一個元素開始,推算出後續任意的元素,這種邏輯其實非常類似generator。
也就是說,上面的函數和generator僅一步之遙。要把fib
函數變成generator,只需要把print(b)
改為yield b
就可以了:
1 def fib(num): 2 n, a, b = 0, 0, 1 3 while n < num: 4 yield b # 將函數變為生成器 5 a, b = b, a+b 6 n += 1 7 return "done" # 異常的時候打印該消息
這就是定義generator的另一種方法。如果一個函數定義中包含yield
關鍵字,那麽這個函數就不再是一個普通函數,而是一個generator:
1 >>> num = fib(10) 2 >>> num 3 <generator object fib at 0x0000022E4EFD88E0> 4 >>> print(num.__next__()) 5 1 6 >>> print(num.__next__()) 7 1 8 >>> print(num.__next__()) 9 2 10 >>> print(num.__next__()) 11 3 12 >>> print(num.__next__()) 13 5 14 >>> print(num.__next__()) 15 8 16 >>> print(num.__next__()) 17 13 18 >>> print(num.__next__()) 19 21 20 >>> print(num.__next__()) 21 34 22 >>> print(num.__next__()) 23 55 24 >>> print(num.__next__()) 25 Traceback (most recent call last): 26 File "<pyshell#65>", line 1, in <module> 27 print(num.__next__()) 28 StopIteration: done
這裏,最難理解的就是generator和函數的執行流程不一樣。函數是順序執行,遇到return
語句或者最後一行函數語句就返回。而變成generator的函數,在每次調用next()
的時候執行,遇到yield
語句返回,再次執行時從上次返回的yield
語句處繼續執行。
f = fib(10)
print(f)
print(f.__next__())
print(f.__next__())
print(f.__next__())
print("從這裏開始,每一個數都由前面緊鄰的兩個數相加得到。")
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
#以下為輸出結果
1
1
2
從這裏開始,每一個數都由前面緊鄰的兩個數相加得到。
3
5
8
13
21
34
55
在上面fib
的例子,我們在循環過程中不斷調用yield
,就會不斷中斷(視頻11節2分20秒)。當然要給循環設置一個條件來退出循環,不然就會產生一個無限數列出來。
同樣的,把函數改成generator後,我們基本上從來不會用next()
來獲取下一個返回值,而是直接使用for
循環來叠代:
1 >>> num = fib(10) 2 >>> for n in num: 3 print(n) 4 1 5 1 6 2 7 3 8 5 9 8 10 13 11 21 12 34 13 55
但是用for
循環調用generator時,發現拿不到generator的return
語句的返回值。如果想要拿到返回值,必須捕獲StopIteration
錯誤,返回值包含在StopIteration
的value
中:
1 >>> g = fib(6) 2 >>> while True: 3 try: 4 x = next(g) 5 print("g:", x) 6 except StopIteration as e: # 該錯誤表示無法繼續返回下一個值了 7 print("Generator return value:", e.value) 8 break 9 #以下為輸出結果 10 g: 1 11 g: 1 12 g: 2 13 g: 3 14 g: 5 15 g: 8 16 Generator return value: done
還可以通過yield實現單線程下的並行計算效果:
1 import time 2 3 4 def customer(name): 5 print("%s準備吃包子啦!" % name) 6 while True: 7 baozi = yield # yield的作用保存當前狀態並返回 8 print("包子[%s]來了!被[%s]吃了!" % (baozi, name)) 9 10 11 def producer(name): 12 c1 = customer("druid") 13 c2 = customer("alex") 14 c1.__next__() # next只 15 c2.__next__() 16 print("老子準備開始吃包子了!") 17 for i in range(10): # 做十個包子 18 time.sleep(1) 19 print("做了一個包子,一人一半。") 20 c1.send(i) # 繼續回到生成器,並且將值傳遞給yield 21 c2.send(i) 22 23 24 producer("druid") 25 26 #以下為輸出結果 27 druid準備吃包子啦! 28 alex準備吃包子啦! 29 老子準備開始吃包子了! 30 做了一個包子,一人一半。 31 包子[0]來了!被[druid]吃了! 32 包子[0]來了!被[alex]吃了! 33 做了一個包子,一人一半。 34 包子[1]來了!被[druid]吃了! 35 包子[1]來了!被[alex]吃了! 36 做了一個包子,一人一半。 37 包子[2]來了!被[druid]吃了! 38 包子[2]來了!被[alex]吃了! 39 做了一個包子,一人一半。 40 包子[3]來了!被[druid]吃了! 41 包子[3]來了!被[alex]吃了! 42 做了一個包子,一人一半。 43 包子[4]來了!被[druid]吃了! 44 包子[4]來了!被[alex]吃了! 45 做了一個包子,一人一半。 46 包子[5]來了!被[druid]吃了! 47 包子[5]來了!被[alex]吃了! 48 做了一個包子,一人一半。 49 包子[6]來了!被[druid]吃了! 50 包子[6]來了!被[alex]吃了! 51 做了一個包子,一人一半。 52 包子[7]來了!被[druid]吃了! 53 包子[7]來了!被[alex]吃了! 54 做了一個包子,一人一半。 55 包子[8]來了!被[druid]吃了! 56 包子[8]來了!被[alex]吃了! 57 做了一個包子,一人一半。 58 包子[9]來了!被[druid]吃了! 59 包子[9]來了!被[alex]吃了!
14-Python-生成器