1. 生成器定義
在Python中,一邊循環一邊計算的機制,稱爲生成器:generator。
2. 爲什麼要有生成器
列表所有數據都在內存中,如果有海量數據的話將會非常耗內存。
如:僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
如果列表元素按照某種算法推算出來,那我們就可以在循環的過程中不斷推算出後續的元素,這樣就不必創建完整的list,從而節省大量的空間。
簡單一句話:我又想要得到龐大的數據,又想讓它佔用空間少,那就用生成器!
3.如何創建生成器
第一種方法很簡單,只要把一個列表生成式的[]
改成()
,就創建了一個generator:
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
創建L
和g
的區別僅在於最外層的[]
和()
,L
是一個list,而g
是一個generator。
方法二, 如果一個函數中包含yield
關鍵字,那麼這個函數就不再是一個普通函數,而是一個generator。調用函數就是創建了一個生成器(generator)對象。
4. 生成器的工作原理
(1)生成器(generator)能夠迭代的關鍵是它有一個next()方法,
工作原理就是通過重複調用next()方法,直到捕獲一個異常。
(2)帶有 yield 的函數不再是一個普通函數,而是一個生成器generator。
可用next()調用生成器對象來取值。next 兩種方式 t.__next__() | next(t)。
可用for 循環獲取返回值(每執行一次,取生成器裏面一個值)
(基本上不會用next()
來獲取下一個返回值,而是直接使用for
循環來迭代)。
(3)yield相當於 return 返回一個值,並且記住這個返回的位置,下次迭代時,代碼從yield的下一條語句開始執行。
(4).send() 和next()一樣,都能讓生成器繼續往下走一步(下次遇到yield停),但send()能傳一個值,這個值作爲yield表達式整體的結果
——換句話說,就是send可以強行修改上一個yield表達式值。比如函數中有一個yield賦值,a = yield 5,第一次迭代到這裏會返回5,a還沒有賦值。第二次迭代時,使用.send(10),那麼,就是強行修改yield 5表達式的值爲10,本來是5的,那麼a=10
感受下yield返回值的過程(關注點:每次停在哪,下次又開始在哪)及send()傳參的通訊過程,
思考None是如何產生的(第一次取值:yield 返回了 i 值 0,停在yield i,temp沒賦到值。第二次取值,開始在print,temp沒被賦值,故打印None,i加1,繼續while判斷,yield 返回了 i 值 1,停在yield i):
好了,話不多說,翠花,上栗子:
1 #encoding:UTF-8
2 def yield_test(n):
3 for i in range(n):
4 yield call(i)
5 print("i=",i)
6 print("Done.")
7
8 def call(i):
9 return i*2
10
11 for i in yield_test(5):
12 print(i,",")
結果:
>>>
0 ,
i= 0
2 ,
i= 1
4 ,
i= 2
6 ,
i= 3
8 ,
i= 4
Done.
>>>
理解的關鍵在於:下次迭代時,代碼從yield的下一條語句開始執行。
總結:
什麼是生成器?
生成器僅僅保存了一套生成數值的算法,並且沒有讓這個算法現在就開始執行,而是我什麼時候調它,它什麼時候開始計算一個新的值,並給你返回。
練習題:
def count_down(n):
while n >= 0:
newn = yield n
print('newn', newn)
if newn:
print('if')
n = newn
print('n =', n)
else:
n -= 1
cd = count_down(5)
for i in cd:
print(i, ',')
if i == 5:
cd.send(3)
結果:
備註:xx = yield yy
摘要:send()的作用是使xx賦值爲發送的值(send的參數),然後讓生成器執行到下個yield..
使用send(params)需要區分情況。
注意:如果生成器未啓動,則必須在使用send()前必須要啓動生成器,而啓動的方法可以是generator.next()或是generator.send(None)執行到第一個yield處,之後就可以使用send(params)不斷傳入值了。
如果是已啓動,則send(params)的作用就是給xx賦值爲發送的值(send的參數),然後讓生成器執行到下個yield。
爲什麼需要send(None),也很好理解,因爲 生成器還沒有走到第一個yield語句,如果我們發生一個真實的值,這時是沒有人去“接收”它的。一旦生成器啓動了,就對象接受(即=號左邊的左值xx接受了),
之後就可以使用send(params)不斷傳入值了。
▲注意,每次的send()都會運行到yield語句,但賦值不會執行,只會有返回值,相當於return後就退出函數了,所以在返回值之後的賦值就不會執行了。