叠代器和生成器
一 叠代和可叠代協議
什麽叫叠代
1234不可以for循環,是因為它不可叠代。那麽如果“可叠代”,就應該可以被for循環了。
這個我們知道呀,字符串、列表、元組、字典、集合都可以被for循環,說明他們都是可叠代的。
我們怎麽來證明這一點呢?
from collections import Iterable l = [1,2,3,4] t = (1,2,3,4) d = {1:2,3:4} s = {1,2,3,4}print(isinstance(l,Iterable)) print(isinstance(t,Iterable)) print(isinstance(d,Iterable)) print(isinstance(s,Iterable))
結合我們使用for循環取值的現象,再從字面上理解一下,其實叠代就是我們剛剛說的,可以將某個數據集內的數據“一個挨著一個的取出來”,就叫做叠代。
可叠代協議
可以被叠代要滿足的要求就叫做可叠代協議。可叠代協議的定義非常簡單,就是內部實現了__iter__方法。
叠代器協議
‘‘‘ dir([1,2].__iter__())是列表叠代器中實現的所有方法,dir([1,2])是列表中實現的所有方法,都是以列表的形式返回給我們的,為了看的更清楚,我們分別把他們轉換成集合, 然後取差集。‘‘‘ #print(dir([1,2].__iter__())) #print(dir([1,2])) print(set(dir([1,2].__iter__()))-set(dir([1,2]))) 結果: {‘__length_hint__‘, ‘__next__‘, ‘__setstate__‘}
我們看到在列表叠代器中多了三個方法,那麽這三個方法都分別做了什麽事呢?
iter_l = [1,2,3,4,5,6].__iter__() #獲取叠代器中元素的長度 print(iter_l.__length_hint__()) #根據索引值指定從哪裏開始叠代 print(‘*‘,iter_l.__setstate__(4)) #一個一個的取值 print(‘**‘,iter_l.__next__()) print(‘***‘,iter_l.__next__())
在for循環中,就是在內部調用了__next__方法才能取到一個一個的值。
那接下來我們就用叠代器的next方法來寫一個不依賴for的遍歷。
l = [1,2,3,4] l_iter = l.__iter__() item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item)
這是一段會報錯的代碼,如果我們一直取next取到叠代器裏已經沒有元素了,就會拋出一個異常StopIteration,告訴我們,列表中已經沒有有效的元素了。
這個時候,我們就要使用異常處理機制來把這個異常處理掉。
l = [1,2,3,4] l_iter = l.__iter__() while True: try: item = l_iter.__next__() print(item) except StopIteration: break
那現在我們就使用while循環實現了原本for循環做的事情,我們是從誰那兒獲取一個一個的值呀?是不是就是l_iter?好了,這個l_iter就是一個叠代器。
叠代器遵循叠代器協議:必須擁有__iter__方法和__next__方法。
我們來看看range()是個啥。首先,它肯定是一個可叠代的對象,但是它是否是一個叠代器?我們來測試一下
print(‘__next__‘ in dir(range(12))) #查看‘__next__‘是不是在range()方法執行之後內部是否有__next__ print(‘__iter__‘ in dir(range(12))) #查看‘__next__‘是不是在range()方法執行之後內部是否有__iter__ from collections import Iterator print(isinstance(range(100000000),Iterator)) #驗證range執行之後得到的結果不是一個叠代器
序列類型:字符串,列表,元組都有下標,你用上述的方式訪問,perfect!但是你可曾想過非序列類型:像字典,集合,文件對象的感受,所以嘛,年輕人,for循環就是基於叠代器協議提供了一個統一的可以遍歷所有對象的方法,即在遍歷之前,先調用對象的__iter__方法將其轉換成一個叠代器,然後使用叠代器協議去實現循環訪問,這樣所有的對象就都可以通過for循環來遍歷了
二.生成器
Python中提供的生成器:
1.生成器函數:常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行
2.生成器表達式:類似於列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表
生成器Generator:
本質:叠代器(所以自帶了__iter__方法和__next__方法,不需要我們去實現)
特點:惰性運算,開發者自定義
生成器函數
一個包含yield關鍵字的函數就是一個生成器函數。yield可以為我們從函數中返回值,但是yield又不同於return,return的執行意味著程序的結束,調用生成器函數不會得到返回的具體的值,而是得到一個可叠代的對象。每一次獲取這個可叠代對象的值,就能推動函數的執行,獲取新的返回值。直到函數執行結束。
import time def genrator_fun1(): a = 1 print(‘現在定義了a變量‘) yield a b = 2 print(‘現在又定義了b變量‘) yield b g1 = genrator_fun1() print(‘g1 : ‘,g1) #打印g1可以發現g1就是一個生成器 print(‘-‘*20) #我是華麗的分割線 print(next(g1)) time.sleep(1) #sleep一秒看清執行過程 print(next(g1))初識生成器
生成器有什麽好處呢?就是不會一下子在內存中生成太多數據
假如我想讓工廠給學生做校服,生產2000000件衣服,我和工廠一說,工廠應該是先答應下來,然後再去生產,我可以一件一件的要,也可以根據學生一批一批的找工廠拿。
而不能是一說要生產2000000件衣服,工廠就先去做生產2000000件衣服,等回來做好了,學生都畢業了。。
def produce(): """生產衣服""" for i in range(2000000): yield "生產了第%s件衣服"%i product_g = produce() print(product_g.__next__()) #要一件衣服 print(product_g.__next__()) #再要一件衣服 print(product_g.__next__()) #再要一件衣服 num = 0 for i in product_g: #要一批衣服,比如5件 print(i) num +=1 if num == 5: break #到這裏我們找工廠拿了8件衣服,我一共讓我的生產函數(也就是produce生成器函數)生產2000000件衣服。 #剩下的還有很多衣服,我們可以一直拿,也可以放著等想拿的時候再拿初識生成器二
更多應用
import time def tail(filename): f = open(filename) f.seek(0, 2) #從文件末尾算起 while True: line = f.readline() # 讀取文件中新的文本行 if not line: time.sleep(0.1) continue yield line tail_g = tail(‘tmp‘) for line in tail_g: print(line)生成器監聽文件輸入的例子
def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count g_avg = averager() next(g_avg) print(g_avg.send(10)) print(g_avg.send(30)) print(g_avg.send(5))計算移動平均值(1)
def init(func): #在調用被裝飾生成器函數的時候首先用next激活生成器 def inner(*args,**kwargs): g = func(*args,**kwargs) next(g) return g return inner @init def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count g_avg = averager() # next(g_avg) 在裝飾器中執行了next方法 print(g_avg.send(10)) print(g_avg.send(30)) print(g_avg.send(5))計算移動平均值(2)_預激協程的裝飾器
yield from
def gen1(): for c in ‘AB‘: yield c for i in range(3): yield i print(list(gen1())) def gen2(): yield from ‘AB‘ yield from range(3) print(list(gen2()))yield from
列表推導式和生成器表達式
#老男孩由於峰哥的強勢加盟很快走上了上市之路,alex思來想去決定下幾個雞蛋來報答峰哥 egg_list=[‘雞蛋%s‘ %i for i in range(10)] #列表解析 #峰哥瞅著alex下的一筐雞蛋,捂住了鼻子,說了句:哥,你還是給我只母雞吧,我自己回家下 laomuji=(‘雞蛋%s‘ %i for i in range(10))#生成器表達式 print(laomuji) print(next(laomuji)) #next本質就是調用__next__ print(laomuji.__next__()) print(next(laomuji))View Code
總結:
1.把列表解析的[]換成()得到的就是生成器表達式
2.列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存
3.Python不但使用叠代器協議,讓for循環變得更加通用。大部分內置函數,也是使用叠代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用叠代器協議訪問對象,而生成器實現了叠代器協議,所以,我們可以直接這樣計算一系列值的和:
sum(x ** 2 for x in xrange(4))
而不用多此一舉的先構造一個列表:
sum([x ** 2 for x in xrange(4)])
本章小結
可叠代對象:
擁有__iter__方法
特點:惰性運算
例如:range(),str,list,tuple,dict,set
叠代器Iterator:
擁有__iter__方法和__next__方法
例如:iter(range()),iter(str),iter(list),iter(tuple),iter(dict),iter(set),reversed(list_o),map(func,list_o),filter(func,list_o),file_o
生成器Generator:
本質:叠代器,所以擁有__iter__方法和__next__方法
特點:惰性運算,開發者自定義
使用生成器的優點:
延遲計算,一次返回一個結果。也就是說,它不會一次生成所有的結果,這對於大數據量處理,將會非常有用。
#列表解析 sum([i for i in range(100000000)])#內存占用大,機器容易卡死 #生成器表達式 sum(i for i in range(100000000))#幾乎不占內存列表解析式和生成器表達式
有效提高代碼可讀性
叠代器和生成器