python之路——迭代器和生成器
楔子
假如我現在有一個列表l=['a','b','c','d','e'],我想取列表中的內容,有幾種方式?
首先,我可以通過索引取值l[0],其次我們是不是還可以用for循環來取值呀?
你有沒有仔細思考過,用索引取值和for循環取值是有著微妙區別的。
如果用索引取值,你可以取到任意位置的值,前提是你要知道這個值在什么位置。
如果用for循環來取值,我們把每一個值都取到,不需要關心每一個值的位置,因為只能順序的取值,并不能跳過任何一個直接去取其他位置的值。
但你有沒有想過,我們為什么可以使用for循環來取值?
for循環內部是怎么工作的呢?
迭代器
返回頂部python中的for循環
要了解for循環是怎么回事兒,咱們還是要從代碼的角度出發。
首先,我們對一個列表進行for循環。
for i in [1,2,3,4]: print(i)上面這段代碼肯定是沒有問題的,但是我們換一種情況,來循環一個數字1234試試
for i in 1234print(i) 結果: Traceback (most recent call last):File "test.py", line 4, in <module>for i in 1234: TypeError: 'int' object is not iterable看,報錯了!報了什么錯呢?“TypeError: 'int' object is not iterable”,說int類型不是一個iterable,那這個iterable是個啥?
假如你不知道什么是iterable,我們可以翻翻詞典,首先得到一個中文的解釋,盡管翻譯過來了你可能也不知道,但是沒關系,我會帶著你一步一步來分析。
返回頂部迭代和可迭代協議
什么叫迭代
現在,我們已經獲得了一個新線索,有一個叫做“可迭代的”概念。
首先,我們從報錯來分析,好像之所以1234不可以for循環,是因為它不可迭代。那么如果“可迭代”,就應該可以被for循環了。
這個我們知道呀,字符串、列表、元組、字典、集合都可以被for循環,說明他們都是可迭代的。
我們怎么來證明這一點呢?
from collections import Iterablel = [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循環取值的現象,再從字面上理解一下,其實迭代就是我們剛剛說的,可以將某個數據集內的數據“一個挨著一個的取出來”,就叫做迭代。
可迭代協議
我們現在是從結果分析原因,能被for循環的就是“可迭代的”,但是如果正著想,for怎么知道誰是可迭代的呢?
假如我們自己寫了一個數據類型,希望這個數據類型里的東西也可以使用for被一個一個的取出來,那我們就必須滿足for的要求。這個要求就叫做“協議”。
可以被迭代要滿足的要求就叫做可迭代協議。可迭代協議的定義非常簡單,就是內部實現了__iter__方法。
接下來我們就來驗證一下:
print(dir([1,2])) print(dir((2,3))) print(dir({1:2})) print(dir({1,2})) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'] ['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update'] 結果總結一下我們現在所知道的:可以被for循環的都是可迭代的,要想可迭代,內部必須有一個__iter__方法。
接著分析,__iter__方法做了什么事情呢?
print([1,2].__iter__())結果 <list_iterator object at 0x1024784a8>執行了list([1,2])的__iter__方法,我們好像得到了一個list_iterator,現在我們又得到了一個新名詞——iterator。
iterator,這里給我們標出來了,是一個計算機中的專屬名詞,叫做迭代器。?
迭代器協議
既什么叫“可迭代”之后,又一個歷史新難題,什么叫“迭代器”?
雖然我們不知道什么叫迭代器,但是我們現在已經有一個迭代器了,這個迭代器是一個列表的迭代器。
我們來看看這個列表的迭代器比起列表來說實現了哪些新方法,這樣就能揭開迭代器的神秘面紗了吧?
''' 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__())這三個方法中,能讓我們一個一個取值的神奇方法是誰?
沒錯!就是__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__方法。
還賬:next和iter方法
如此一來,關于迭代器和生成器的方法我們就還清了兩個,最后我們來看看range()是個啥。首先,它肯定是一個可迭代的對象,但是它是否是一個迭代器?我們來測試一下
print('__next__' in dir(range(12))) #查看'__next__'是不是在range()方法執行之后內部是否有__next__ print('__iter__' in dir(range(12))) #查看'__next__'是不是在range()方法執行之后內部是否有__next__from collections import Iterator print(isinstance(range(100000000),Iterator)) #驗證range執行之后得到的結果不是一個迭代器 range函數的返回值是一個可迭代對象?
為什么要有for循環
基于上面講的列表這一大堆遍歷方式,聰明的你立馬看除了端倪,于是你不知死活大聲喊道,你這不逗我玩呢么,有了下標的訪問方式,我可以這樣遍歷一個列表啊
l=[1,2,3]index=0 while index < len(l):print(l[index])index+=1#要毛線for循環,要毛線可迭代,要毛線迭代器沒錯,序列類型字符串,列表,元組都有下標,你用上述的方式訪問,perfect!但是你可曾想過非序列類型像字典,集合,文件對象的感受,所以嘛,年輕人,for循環就 是基于迭代器協議提供了一個統一的可以遍歷所有對象的方法,即在遍歷之前,先調用對象的__iter__方法將其轉換成一個迭代器,然后使用迭代器協議去實現循環訪 問,這樣所有的對象就都可以通過for循環來遍歷了,而且你看到的效果也確實如此,這就是無所不能的for循環,覺悟吧,年輕人
生成器
返回頂部初識生成器
我們知道的迭代器有兩種:一種是調用方法直接返回的,一種是可迭代對象通過執行iter方法得到的,迭代器有的好處是可以節省內存。
如果在某些情況下,我們也需要節省內存,就只能自己寫。我們自己寫的這個能實現迭代器功能的東西就叫生成器。
?
Python中提供的生成器:
1.生成器函數:常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開 的地方繼續執行
2.生成器表達式:類似于列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表
?
生成器Generator:
本質:迭代器(所以自帶了__iter__方法和__next__方法,不需要我們去實現)
特點:惰性運算,開發者自定義
?
?
生成器函數
一個包含yield關鍵字的函數就是一個生成器函數。yield可以為我們從函數中返回值,但是yield又不同于return,return的執行意味著程序的結束,調用生成器函數不會得 到返回的具體的值,而是得到一個可迭代的對象。每一次獲取這個可迭代對象的值,就能推動函數的執行,獲取新的返回值。直到函數執行結束。
import time def genrator_fun1():a = 1print('現在定義了a變量')yield ab = 2print('現在又定義了b變量')yield bg1 = genrator_fun1() print('g1 : ',g1) #打印g1可以發現g1就是一個生成器 print('-'*20) #我是華麗的分割線 print(next(g1)) time.sleep(1) #sleep一秒看清執行過程 print(next(g1)) 初識生成器函數?
生成器有什么好處呢?就是不會一下子在內存中生成太多數據
假如我想讓工廠給學生做校服,生產2000000件衣服,我和工廠一說,工廠應該是先答應下來,然后再去生產,我可以一件一件的要,也可以根據學生一批一批的找工? 廠拿。
而不能是一說要生產2000000件衣服,工廠就先去做生產2000000件衣服,等回來做好了,學生都畢業了。。。
?
更多應用
import timedef tail(filename):f = open(filename)f.seek(0, 2) #從文件末尾算起while True:line = f.readline() # 讀取文件中新的文本行if not line:time.sleep(0.1)continueyield linetail_g = tail('tmp') for line in tail_g:print(line) 生成器監聽文件輸入的例子send
def generator():print(123)content = yield 1print('=======',content)print(456)yield2g = generator() ret = g.__next__() print('***',ret) ret = g.send('hello') #send的效果和next一樣 print('***',ret)#send 獲取下一個值的效果和next基本一致 #只是在獲取下一個值的時候,給上一yield的位置傳遞一個數據 #使用send的注意事項# 第一次使用生成器的時候 是用next獲取下一個值# 最后一個yield不能接受外部的值 def averager():total = 0.0count = 0average = Nonewhile True:term = yield averagetotal += termcount += 1average = total/countg_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 greturn inner@init def averager():total = 0.0count = 0average = Nonewhile True:term = yield averagetotal += termcount += 1average = total/countg_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 cfor i in range(3):yield iprint(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)) 峰哥與alex的故事總結:
1.把列表解析的[]換成()得到的就是生成器表達式
2.列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存
3.Python不但使用迭代器協議,讓for循環變得更加通用。大部分內置函數,也是使用迭代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用迭代? ? ? ? ? 器協議訪問對象,而生成器實現了迭代器協議,所以,我們可以直接這樣計算一系列值的和:
sum(x ** 2 for x in range(4))而不用多此一舉的先構造一個列表:
sum([x ** 2 for x in range(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__方法
特點:惰性運算,開發者自定義
使用生成器的優點:
1.延遲計算,一次返回一個結果。也就是說,它不會一次生成所有的結果,這對于大數據量處理,將會非常有用。
?
#列表解析 sum([i for i in range(100000000)])#內存占用大,機器容易卡死#生成器表達式 sum(i for i in range(100000000))#幾乎不占內存 列表解析式和生成器表達式?
2.提高代碼可讀性
?
?
生成器相關的面試題
生成器在編程中發生了很多的作用,善用生成器可以幫助我們解決很多復雜的問題
除此之外,生成器也是面試題中的重點,在完成一些功能之外,人們也想出了很多魔性的面試題。
接下來我們就來看一看~
?
?
?
?
轉載于:https://www.cnblogs.com/xiaole-7890/p/9332191.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的python之路——迭代器和生成器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 南邮CTF-MISC-Remove Bo
- 下一篇: qt5.10 for android 使