1. 程式人生 > >14-Python-生成器

14-Python-生成器

所有 無限 urn 完整 結果 狀態 ext 退出 著名

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 for
i 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__()) 15
36 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錯誤,返回值包含在StopIterationvalue中:

 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-生成器