基礎(chǔ)知識
生成器是python的一個特別特的特性衣式,在許多場合都有重要應(yīng)用爪膊。比如range函數(shù)產(chǎn)生的就是一個生成器捡遍。其主要的好處就是降低了內(nèi)存的占用。為什么呢眨业?拿range函數(shù)來講吧急膀,它的目的是生成一系列的數(shù)。假如我們想生成一列數(shù)0龄捡,1卓嫂,2,3墅茉,4命黔,則(以下兩個緊跟的代碼塊中呜呐,第一個是實際的代碼,第二個是輸出)
range(5)
range(0, 5)
欸悍募?怎么回事蘑辑,輸出的不是0,1坠宴,2洋魂,3,4,而是一個函數(shù)喜鼓。這個函數(shù)實際上便是一個生成器副砍。我們可以采用下面的方法把它展開
list(range(5))
[0, 1, 2, 3, 4]
或者使用一個for循環(huán)
for i in range(5):
print(i)
0
1
2
3
4
生成器常常與for循環(huán)結(jié)合使用。在上述循環(huán)過程中庄岖,每循環(huán)一步豁翎,range產(chǎn)生一個數(shù),直至range拋出一個結(jié)束的異常隅忿,for捕獲異常后結(jié)束循環(huán)心剥。
通過運行步驟,我們可以明白生成器節(jié)省空間的道理所在背桐。range并沒有一次性將數(shù)列都生成优烧,而是逐漸生成。
自制生成器
為了進一步掌握生成器的編寫方式链峭,我們一起來實現(xiàn)range函數(shù)畦娄。當(dāng)然這不一定是python內(nèi)部的實現(xiàn)。如前文所述弊仪,range函數(shù)的作用是生成一列數(shù)熙卡。range的原本功能可以指定起始數(shù)、終止數(shù)和間隔撼短。為了演示突出生成器的重點再膳,我們將實現(xiàn)一個簡化版的range挺勿,只給一個輸入n曲横,讓其生成由0到n-1的數(shù)。并且不瓶,這個函數(shù)只是我們自己使用禾嫉,就不做輸入合法性檢查了。閑言少敘蚊丐,先看代碼
def _range(n):
step = 0
while step < n:
yield step
step = step + 1
print(list(_range(5)))
[0, 1, 2, 3, 4]
注釋講解版函數(shù)如下
# 為了與python內(nèi)置range函數(shù)進行區(qū)分熙参,我們將其命名為_range.
# 那么如果你說我就不區(qū)分會怎么樣?如果不區(qū)分的話麦备,那么咱在這里定義的函數(shù)便會覆蓋原函數(shù)孽椰。
def _range(n):
# 采用一個變量暫存步數(shù)
step = 0
# 采用一個while循環(huán)昭娩,產(chǎn)生數(shù)據(jù)。
# 當(dāng)步數(shù)小于輸入值n時黍匾,執(zhí)行循環(huán)迭代
while step < n:
# 這是迭代器最關(guān)鍵的一步栏渺。yield表示輸出此洲。
# 每當(dāng)調(diào)用一次_range缩擂,便會執(zhí)行到一個yield處著瓶,產(chǎn)生一個數(shù)據(jù)漂彤,同時暫停函數(shù)運行祈争,直至再次調(diào)用苟弛。
# 稍后會給出另外一個版本备典,加強大家在這方面的理解诫欠。
yield step
# 當(dāng)再次調(diào)用時升薯,會從這里開始莱褒,而非函數(shù)頭部。步數(shù)加1涎劈,繼續(xù)迭代
step = step + 1
# 調(diào)用并將產(chǎn)生的數(shù)據(jù)展開后輸出
print(list(_range(5)))
為了進一步加強大家對生成器的理解保礼,接下來我們將上述版本稍作修改。我們想要產(chǎn)生一個0责语,0炮障,1,1坤候,2胁赢,2.。白筹。智末。n-1,n-1的數(shù)列。該如何是好徒河?且看
def _range(n):
step = 0
while step < n:
yield step
yield step
step = step + 1
print(list(_range(5)))
[0, 0, 1, 1, 2, 2, 3, 3, 4, 4]
同時采用了兩個yield系馆,每當(dāng)對_range函數(shù)調(diào)用一次,便從函數(shù)暫停的位置運行至下一個yield處顽照,產(chǎn)生數(shù)據(jù)由蘑,隨后輸出。
接下來再舉一個例子代兵。我們產(chǎn)生一個0尼酿,0,1植影,0裳擎,1,2思币,0鹿响,1羡微,2,3等等惶我。這就要用到y(tǒng)ield from這個語法了拷淘。它是生成器中的生成器。
def _range(n):
step = 0
while step < n:
# range是另外一個生成器指孤,yield from表示函數(shù)運行到此處時启涯,進入range函數(shù)繼續(xù)執(zhí)行,直至range函數(shù)執(zhí)行完畢恃轩。
yield from range(step)
step = step + 1
print(list(_range(5)))
[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]
如果不用yield from會怎么樣结洼?想想。答案如下:
def _range(n):
step = 0
while step < n:
# range是另外一個生成器叉跛,yield from表示函數(shù)運行到此處時松忍,進入range函數(shù)繼續(xù)執(zhí)行,直至range函數(shù)執(zhí)行完畢筷厘。
yield range(step)
step = step + 1
print(list(_range(5)))
for i in _range(5):
print(i)
for i in _range(5):
print(list(i))
[range(0, 0), range(0, 1), range(0, 2), range(0, 3), range(0, 4)]
range(0, 0)
range(0, 1)
range(0, 2)
range(0, 3)
range(0, 4)
[]
[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
一個實際應(yīng)用
不知道大家對生成器的基本原理及用法是否理解鸣峭?下面我用一個更為實際的例子講解生成器的妙用。
我在做數(shù)據(jù)處理時酥艳,單個數(shù)據(jù)文件可能非常大摊溶,直接讀進內(nèi)存很困難。這時候充石,便可采用生成器逐步讀取處理莫换。如
import os
def getFrame(filename):
pos = 0
frameSize = 100
fullSize = os.path.getsize(filename)
with open(filename, 'rb') as fid:
while pos < fullSize:
fid.seek(pos)
yield fid.read(frameSize)
pos = pos + frameSize
for frame in getFrame('SomeFilePath'):
print(frame)
對于以上代碼,我不過多講解骤铃。大家對于其中有不明白的地方拉岁,可以自行查找?guī)椭謨曰蛘甙俣取H绻€有不明白的地方惰爬,可以留言喊暖。其中大家可以重點理解with用法,這是在文件讀取時常用并且非常好用的一個語法撕瞧。
jupyter notebook原文件位于https://gitee.com/bolang/python-lesson.git