生成器與叠代器
1>叠代器協議
1.叠代器協議是指:對象必須提供一個next方法,執行該方法要麽返回叠代中的下一項,
要麽就引起一個StopIteration異常,以終止叠代 (只能往後走不能往前退)
2.可叠代對象:實現了叠代器協議的對象(如何實現:對象內部定義一個__iter__()方法)
3.協議是一種約定,可叠代對象實現了叠代器協議,python的內部工具
(如for循環,sum,min,max函數等)使用叠代器協議訪問對象。
2>for循環機制
for循環的本質:循環所有對象,全都是使用叠代器協議。for循環就是基於叠代器協議提供了一個統一的可以遍歷所有對象的方法,即在遍歷之前,
列表,字符串,元組,字典,集合,文件對象等本質上來說都不是可叠代對象,
在使用for循環的時候內部是先調用他們內部的_iter_方法,使他們變成了可叠代對象,然後在使用可叠代對象的_next_方法依次循環元素,當元素循環完時,會觸發StopIteration異常,for循環會捕捉到這種異常,終止叠代。
3>訪問方式
1.下標訪問方式
l = [‘lebron‘, ‘ingram‘, ‘lozon‘, ‘kuzma‘]
print(l[1])
print(l[2])
2.遵循叠代器協議訪問方式
l = [‘lebron‘, ‘ingram‘, ‘lozon‘, ‘kuzma‘]
l_iter = l.__iter__()
print(l_iter.__next__())
print(l_iter.__next__())
print(l_iter.__next__())
print(l_iter.__next__())
print(l_iter.__next__()) #超出邊界報錯:StopIteration
3.for循環訪問方式
for循環l本質就是遵循叠代器協議的訪問方式,先調用l_iter = l.__iter__()方法,或者直接l_iter = iter(l),然後依次執行l_iter.next(),直到for循環捕捉到StopIteration終止循環
l = [‘lebron‘, ‘ingram‘, ‘lozon‘, ‘kuzma‘]
for i in l: #l_iter = l.__iter__()
print(i) #print(l_iter.__next__())
4.用while去模擬for循環做的事情
l = [‘lebron‘, ‘ingram‘, ‘lozon‘, ‘kuzma‘]
l_iter = l.__iter__()
while 1:
try:
print(l_iter.__next__())
except StopIteration:
print(‘叠代完畢,循環終止‘)
break
5>for循環的意義
序列類型字符串,列表,元組都有下標,用下標訪問方式。但是非序列類型像字典,集合,文件對象,for循環就是基於叠代器協議提供了一個統一的可以遍歷所有對象的方法,即在遍歷之前,先調用對象的__iter__方法將其轉換成一個叠代器,然後使用叠代器協議去實現循環訪問,這樣所有的對象就都可以通過for循環來遍歷了。
6>生成器初探
生成器:可以理解為一種數據類型,這種數據類型自動實現了叠代器協議(其他的數據類型需要調用自己內置的__iter__方法),所以生成器就是可叠代對象
1.生成器函數:常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行
2.生成器表達式:類似於列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表
3.生成器的優點:Python使用生成器對延遲操作提供了支持。所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果。這也是生成器的主要好處。
生成器小結:
1.是可叠代對象
2.實現了延遲計算,省內存
3.生成器本質和其他的數據類型一樣,都是實現了叠代器協議,只不過生成器附加了一個延遲計算省內存的好處,其余的可叠代對象沒有這點好處。
7>生成器函數
import time
def all_star():
print(‘2019年全明星正賽西部首發‘)
print(‘前場‘)
time.sleep(2)
yield ‘勒布朗·詹姆斯‘
time.sleep(5)
yield ‘凱文·杜蘭特‘
time.sleep(5)
yield ‘保羅·喬治‘
time.sleep(2)
print(‘後場‘)
time.sleep(2)
yield ‘斯蒂芬·庫裏‘
time.sleep(5)
yield ‘詹姆斯·哈登‘
time.sleep(2)
yield ‘替補陣容名單將於5天後公布‘
all_star_player = all_star()
print(all_star_player.__next__())
print(all_star_player.__next__())
print(all_star_player.__next__())
print(all_star_player.__next__())
print(all_star_player.__next__())
print(all_star_player.__next__())
print(all_star_player.__next__())
def lay_eggs(num):
egg_list = []
for egg in range(num):
egg_list.append(‘蛋%s‘ %egg)
return egg_list
yikuangdan=lay_eggs(10) #我們拿到的是蛋
print(yikuangdan)
def lay_eggs(num):
for egg in range(num):
res=‘蛋%s‘ %egg
yield res
print(‘下完一個蛋‘)
laomuji=lay_eggs(10)#我們拿到的是一只母雞
print(laomuji)
print(laomuji.__next__())
print(laomuji.__next__())
print(laomuji.__next__())
egg_l=list(laomuji)
print(egg_l)
#演示只能往後不能往前
#演示蛋下完了,母雞就死了
8>生成器表達式和列表解析
1.三元表達式
name = ‘lebronjames‘
mvp = ‘牛逼‘ if name == ‘lebronjames‘ else ‘垃圾‘
print(mvp)
2.列表解析
生成所有元素並組成一個列表
l1_zgj = [‘總冠軍+%s‘ % i for i in range(10)] #列表解析
l2_zgj = [‘總冠軍+%s‘ % i for i in range(10) if i < 7] #包含三元表達式
print(l1_zgj)
print(l2_zgj)
3.生成器表達式
l_mvp = (‘總冠軍+%s‘ % i for i in range(10)) #生成器表達式
print(l_mvp.__next__())
print(next(l_mvp))
#將列表解析的中括號換成小括號就成了生成器表達式
4.列表解析與生成器表達式的區別
列表解析相當於一次性生成所有的元素,而生成器表達式一次只生成一個,之後函數為掛起狀態,等待下一次的next方法,再生成下一個。
#湖人隊由於詹姆斯的強勢加盟很快走上了復興之路,老板珍妮巴斯思來想去決定下幾個雞蛋來報答詹姆斯。
egg_list = [‘雞蛋%s‘ %i for i in range(10)] #列表解析
#詹姆斯瞅著珍妮巴斯下的一筐雞蛋,捂住了鼻子,說了句:哥,你還是給我只母雞吧,我自己回家下
laomuji = (‘雞蛋%s‘ %i for i in range(10))#生成器表達式
print(laomuji)
print(next(laomuji)) #next本質就是調用__next__
print(laomuji.__next__())
print(next(laomuji))
5.總結
1.把列表解析的[]換成()得到的就是生成器表達式
2.列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存
3.Python不但使用叠代器協議,讓for循環變得更加通用。大部分內置函數,也是使用叠代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用叠代器協議訪問對象,而生成器實現了叠代器
協議,所以,我們可以直接這樣計算一系列值的和:
print(sum(x ** 2 for x in range(4)))
而不用多此一舉的先構造一個列表:
print(sum([x ** 2 for x in range(4)]))
9>生成器總結
綜上已經對生成器有了一定的認識,下面我們以生成器函數為例進行總結
- 語法上和函數類似:生成器函數和常規函數幾乎是一樣的。它們都是使用def語句進行定義,差別在於,生成器使用yield語句返回一個值,而常規函數使用return語句返回一個值
- 自動實現叠代器協議:對於生成器,Python會自動實現叠代器協議,以便應用到叠代背景中(如for循環,sum函數)。由於生成器自動實現了叠代器協議,所以,我們可以調用它的next方法,並且, 在沒有值可以在沒有返回的時候,生成器自動產生StopIteration異常
- 狀態掛起:生成器使用yield語句返回一個值。yield語句掛起該生成器函數的狀態,保留足夠的信息,以便之後從它離開的地方繼續執行
優點一:生成器的好處是延遲計算,一次返回一個結果。也就是說,它不會一次生成所有的結果,這對於大數據量處理,將會非常有用。
#列表解析
sum([i for i in range(100000000)])#內存占用大,機器容易卡死
#生成器表達式
sum(i for i in range(100000000))#幾乎不占內存
優點二:生成器還能有效提高代碼可讀性
不使用叠代器
#求球星名字所在的位置
def find_mvpp(list1):
player = []
if list1:
for index, person in enumerate(list1, 1):
player.append(index)
return player
find = find_mvpp((‘lebron james‘, ‘james harden‘, ‘pual gorge‘, ‘zimuge‘))
print(find)
-----------------------------------------------------------------------------------------
#求一段文字中,每個單詞出現的位置
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text, 1):
if letter == ‘ ‘:
result.append(index)
return result
print(index_words(‘hello alex da sb‘))
使用叠代器
def find_mvp(list1):
if list1:
for index, preson in enumerate(list1, 1):
yield index
find = find_mvp((‘lebron james‘, ‘james harden‘, ‘pual gorge‘, ‘zimuge‘))
print(find.__next__())
print(find.__next__())
print(find.__next__())
print(find.__next__())
-----------------------------------------------------------------------------------------
def index_words(text):
if text:
yield 0
for index, letter in enumerate(text, 1):
if letter == ‘ ‘:
yield index
g=index_words(‘hello alex da sb‘)
print(g)
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
#print(g.__next__())#報錯
這裏,至少有兩個充分的理由說明 ,使用生成器比不使用生成器代碼更加清晰:
- 使用生成器以後,代碼行數更少。大家要記住,如果想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數越少越好
- 不使用生成器的時候,對於每次結果,我們首先看到的是result.append(index),其次,才是index。也就是說,我們每次看到的是一個列表的append操作,只是append的是我們想要的結果。使用生成器的時候,直接yield index,少了列表append操作的幹擾,我們一眼就能夠看出,代碼是要返回index。
這個例子充分說明了,合理使用生成器,能夠有效提高代碼可讀性。只要大家完全接受了生成器的概念,理解了yield語句和return語句一樣,也是返回一個值。那麽,就能夠理解為什麽使用生成器比不使用生成器要好,能夠理解使用生成器真的可以讓代碼變得清晰易懂。
生成器與叠代器