Python小白學習之路(二十二)—【生成器】
一.什麼是生成器?
生成器可以理解成是一種資料型別,特殊地是生成器可以自動實現迭代器協議
其他的資料型別需要呼叫自己內建的__iter__方法
所以換種說法,生成器就是可迭代物件
!回憶:很重要的迭代器協議
物件必須提供一個 next 方法,執行該方法要麼返回迭代中的下一項,
要麼就引起一個Stoplteration異常,以終止迭代(只能往後走不能往前退)
二.生成器的分類(兩類)
python中生成器的表現形式
python中提供生成器的方式
一類是生成器函式;另一類是生成器表示式
第一類:關於生成器函式
- 與常規函式定義相同。但是返回值時使用 yield 而不是return。
- yield語句一次返回一個結果,可以進行多次返回(而return只能返回一次)
- yield每次返回一個結果,在每個結果中間,掛起函式的狀態(其實就是記住我函式執行到哪一行了)
#舉例: def test (): yield 1 g = test()#並不會執行test()函式,需要通過 g.__next__()方法來觸發生成器函式執行 print(g) print(g.__next__()) #執行結果 <generator object test at 0x0051AA70> 1
在說生成器表示式之前,補充三元表示式和列表解析
三元表示式:(顧名思義,就是有三個元素唄)
以前我們是這麼寫程式的:
name = 'alex' if name == 'alex': print('Ok') else: print('Not ok')
利用三元表示式我們是這麼寫程式的:
name = 'alex' res = 'Ok' if name == 'alex' else 'Not ok'#三元表示式 print(res) #執行結果 Ok
(每一圈為一個元)
列表解析:
列表解析式的語法格式為:
- [i操作 for i in 列表 if 表示式1 and 表示式2]
- (其實就是用中括號[]將三元表示式框起來)
舉例理解:
#我要通過程式下10個雞蛋
#以前我是這麼寫的
egg_list = [] for i in range(10): egg_list.append('雞蛋%s' %i) print(egg_list) #執行結果 ['雞蛋0', '雞蛋1', '雞蛋2', '雞蛋3', '雞蛋4', '雞蛋5', '雞蛋6', '雞蛋7', '雞蛋8', '雞蛋9']
#通過列表解析式我是這麼寫程式的
l0 = [ '雞蛋%s' %i for i in range(10) ] print(l0) #執行結果 ['雞蛋0', '雞蛋1', '雞蛋2', '雞蛋3', '雞蛋4', '雞蛋5', '雞蛋6', '雞蛋7', '雞蛋8', '雞蛋9'] l1 = [ '雞蛋%s' %i for i in range(10) if i < 5 ] print(l1) #執行結果 ['雞蛋0', '雞蛋1', '雞蛋2', '雞蛋3', '雞蛋4'] l2 = [ '雞蛋%s' %i for i in range(10) if i < 3 or i > 7] print(l2) #執行結果 ['雞蛋0', '雞蛋1', '雞蛋2', '雞蛋8', '雞蛋9']
總結:列表解析式優缺點
優點:取值方便(如果列表的長度較小時使用列表解析會很方便,)
缺點:如果列表的長度很大的時候,使用列表解析會佔用很多的記憶體資源,此時可以使用生成器表示式來節省記憶體資源
第二類:關於生成器表示式
生成器表示式:(就是將列表解析式的中括號變成圓括號)
#舉例: l0 = ('雞蛋%s' %i for i in range(10)) print(l0) print(l0.__next__()) print(l0.__next__()) print(l0.__next__()) #執行結果 <generator object <genexpr> at 0x0045AA70> 雞蛋0 雞蛋1 雞蛋2
小結:
1.將列表解析式的 [] 換成 () 得到的就是生成器表示式
2.列表解析式與生成器表示式都是一種便利的程式設計方式,只不過生成器表示式更節省記憶體
3.python使用迭代器協議讓 for 迴圈變得更加通用。大部分內建函式也是使用迭代器協議來訪問物件的
舉例:
sum 函式
sum(x ** 2 for x in range(4))#sum 直接按照迭代器協議訪問物件(類比for迴圈)
sum([x ** 2 for x in range(4)])#所以並不需要將物件 x ** 2 for x in rang(4) 加上一個中括號變成列表解析式,將所有的值取出來構成一個列表再進行求和運算
三、通過兩段程式程式碼來感受一下生成器的優勢
#今天所舉得列子不是下蛋就是吃包子(視訊課上老師就是這麼講的)
#我也深深的懷疑
#為什麼老師這麼鍾愛吃包子和下雞蛋
#下蛋程式一: def xiadan(): res = [] for i in range(10000): res.append('雞蛋%s' %i) return res print(xiadan()) #缺點一:佔用空間較大 #缺點二:效率低
#下蛋程式二: def xiadan(): for i in range(10000): yield '雞蛋%s' %i lmj = xiadan() print(lmj.__next__())
#第一段程式是一旦執行 xiadan()這個函式,先下了10000個雞蛋來佔用記憶體空間,在去執行其他操作 #第二段程式是通過生成器函式yield來返回我所需要的雞蛋,我邊用(通過__next__() 觸發生成器函式)雞蛋,邊下雞蛋
以生成器函式為例,對生成器進行總結
- 語法上和函式類似: 生成器函式和常規函式幾乎是一樣的。它們都是使用def語句進行定義
差別在於生成器使用yield語句進行返回一個值,而常規函式使用return語句返回一個值
- 自動實現迭代器協議: 對於生成器,python會自動實現迭代器協議,以便應用到迭代器背景中
由於生成器自動實現了迭代器協議,所以我們可以直接呼叫它的next方法,並且在沒有值可以返回的時候生成器自動生成Stoplteration異常
- 狀態掛起: 生成器使用yield語句返回一個值。yield 語句掛起該生成器函式的狀態,保留足夠的資訊,以便之後從它離開的地方繼續執行
優點一:
生成器的好處就是延遲計算,一次返回一個結果。也就是說它不會一次生成所有的結果,這對於大資料量
處理非常有用(前面下雞蛋的例子)
優點二:
生成器還能提高程式碼可讀性
1.使用生成器以後,程式碼行數更少(在保證程式碼可讀性的前提下,程式碼行數越少越好)
2.不適用生成器,對於每次結果,我們首先看到的是result.append(index),其次才是index
也就是說,我們每次看到的,是一個列表的append操作,只是append的是我們想要的結果。
使用生成器的時候,直接yield index,少了列表的append操作的干擾,我們一眼能夠看出,程式碼是要進行什麼操作。
四、觸發生成器執行的三種方式
-
- 方式一:__next__()
- 方式二:next()
- 方式三:send()
#舉例: def xiadan(): for i in range(10000): yield '雞蛋%s' %i g = xiadan() print(g.__next__()) print(next(g)) print(g.send(None)) #執行結果 雞蛋0 雞蛋1 雞蛋2
關於 send() 總結來源於一下文章並且結合自己的理解
看到一篇關於 yield 總結特別好的文章
連結:https://www.cnblogs.com/renpingsheng/p/8635777.html
send()必須傳一個引數,可為 None 或者 其他值
作用:
1. yield相當於return ,控制的是函式的返回值
2. x = yield的另外一個特性,接受send傳過來的值,賦值給 x
舉例理解: def test(): print('開始執行函式') first = yield print('第一次', first) second = yield print('第二次', second) yield g = test() print(next(g)) print(g.send(1)) print(g.send(2))
#程式執行過程分析 # 1.程式開始執行以後,因為test函式中有yield關鍵字,所以test函式並不會真的執行,而是先得到一個生成器g. # 2.直到呼叫next方法,test函式正式開始執行,先執行test函式中的print方法,列印開始執行函式。然後執行first = yield # 3.程式遇到yield關鍵字,程式暫停,此時next(g)語句執行完成,列印next(g)執行結果,即yield傳回的結果,為None # 4.程式執行g.send(1),程式會從yield關鍵字那一行繼續向下執行,send會把1這個值傳遞給yield # 5.yield接收到send方法傳遞過來的值,然後由yield賦值給first變數 # 6.由於send方法中包含next()方法,所以程式會繼續向下執行執行print方法,然後再次遇到yield關鍵字,程式暫停,此時g.send(1)語句執行完成,列印g.send(1)執行結果,即yield傳回的結果,為None # 7.程式執行g.send(2),程式會從yield關鍵字那一行繼續向下執行,send會把2這個值傳遞給yield # 8.yield接收到send方法傳遞過來的值,然後由yield賦值給second變數 # 9.由於send方法中包含next()方法,所以程式會繼續向下執行執行print方法,然後再次遇到yield關鍵字,程式暫停,此時g.send(2)語句執行完成,列印g.send(2)執行結果,即yield傳回的結果,為None
寫在後面:
珍愛眼睛 遠離電子產品
從上了研究生階段 眼睛就開始有虹膜炎
隔段時間就來打擾我
不能看電腦不能看手機不能看強光
還拼命流眼淚
我也真是佩服自己
正好這個階段我就在看書學python 也抽時間看了 許三觀賣血記
文學素養還是要培養的
我要做祖國新時代的四有新人 有文化 有道德 有。。。還有什麼來著
哈哈
愛吃火鍋的人運氣不會太差
愛吃火鍋的人怎麼可能輕易放棄