閱讀目錄
楔子
假如我如今有一個列表l=['a','b','c','d','e'],我想取列表中的內容,有幾種方式?javascript
首先,我能夠經過索引取值l[0],其次咱們是否是還能夠用for循環來取值呀?html
你有沒有仔細思考過,用索引取值和for循環取值是有着微妙區別的。java
若是用索引取值,你能夠取到任意位置的值,前提是你要知道這個值在什麼位置。python
若是用for循環來取值,咱們把每個值都取到,不須要關心每個值的位置,由於只能順序的取值,並不能跳過任何一個直接去取其餘位置的值。面試
但你有沒有想過,咱們爲何可使用for循環來取值?編程
for循環內部是怎麼工做的呢?app
迭代器
python中的for循環
要了解for循環是怎麼回事兒,我們仍是要從代碼的角度出發。ssh
首先,咱們對一個列表進行for循環。ide
for i in [1,2,3,4]: print(i)
上面這段代碼確定是沒有問題的,可是咱們換一種狀況,來循環一個數字1234試試函數
for i in 1234 print(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 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循環取值的現象,再從字面上理解一下,其實迭代就是咱們剛剛說的,能夠將某個數據集內的數據「一個挨着一個的取出來」,就叫作迭代。
可迭代協議
咱們如今是從結果分析緣由,能被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方法