【python小技巧4】for循環(huán)原理與迭代器的實現(xiàn)

對于新手,for 循環(huán)無非 for i in range(10):? for i in lst: 等,那你知道 for 循環(huán)真正的原理嗎?
目錄
你需要知道的...
for 循環(huán)的工作原理(2種情況)
如何寫一個可迭代對象和迭代器

你需要知道的...
for 循環(huán)的語法永遠(yuǎn)是?for?xxx?in?xxx,for 循環(huán)又稱遍歷循環(huán),本質(zhì)上一切?for 循環(huán),都是在遍歷一個對象,無論是 range 還是 list。
內(nèi)置函數(shù) 與 類的魔術(shù)方法?的關(guān)系
python 里有很多“魔術(shù)方法”,它們都有一個共同點:以雙下劃線開頭,以雙下劃線結(jié)尾,如 __str__,__int__,__iter__,__next__ 等。(這也是為什么不推薦新手用 __xxx__ 這種當(dāng)變量名的原因)
其中有很多魔術(shù)方法可被對應(yīng)的內(nèi)置函數(shù)調(diào)用,如 str(), int(),?iter(),?next() 等。
本文還涉及了一個魔術(shù)方法 __getitem__
obj.__getitem__(item)? <==>? obj[item],就是列表的取元素操作
可迭代對象 與 迭代器
可迭代對象指實現(xiàn)?__iter__ 方法,一般保存了數(shù)據(jù),如 list 等。
迭代器指實現(xiàn)了 __next__ 方法,一般保存了迭代的狀態(tài)(如迭代到哪一個),如 list_iterator 等。
嚴(yán)格定義如上,但有一條規(guī)定:迭代器應(yīng)該也是可迭代的。換句話說:如果一個對象實現(xiàn)了 __next__ 方法,那它也應(yīng)該實現(xiàn) __iter__ 方法,大部分就是返回自己。
可迭代對象?與?迭代器的概念一定要記住,很重要!??!
如何檢測一個對象是否是迭代器或可迭代?使用內(nèi)置模塊?collections(新版建議用collections.abc 模塊)?下的?Iterable 和 Iterator 就可以,具體用法:
????????注意:字符串、列表、元組、字典、集合等是可迭代的,但不是迭代器!

假設(shè)有一個循環(huán):
以下將在這個循環(huán)的基礎(chǔ)上講解
第一種情況:
如果 obj 是可迭代的,即實現(xiàn)了 __iter__ 方法,嘗試調(diào)用 iter(obj) 得到 obj 的迭代器,假設(shè)迭代器是 iterator。
然后不斷調(diào)用 next(iterator),返回值就是迭代出來的值,直到遇到?StopIteration,停止循環(huán)。
這種情況下上面的代碼與下面是等價的:
第二種情況:
如果 obj 沒有實現(xiàn) __iter__ 方法,則康康有沒有實現(xiàn) __getitem__ 方法。
如果有,則從 0 開始,依次遞增 1 來調(diào)用 __getitem__ 方法,返回值就是迭代出來的值,直到遇到?StopIteration,停止調(diào)用。
這種情況下上面的代碼與下面是等價的:
如果以上兩種方法都行不通,那就不能用 for 循環(huán)(會報TypeError: 'xxx' object is not iterable)

下面再來講一講如何實現(xiàn)可迭代對象和迭代器
實現(xiàn)可迭代對象和迭代器
我們試著模仿 range 函數(shù)寫一個生成等差數(shù)列的可迭代對象
現(xiàn)在它既不是迭代器,也不可迭代
我們試著讓它的迭代器就是它自己(實現(xiàn) __iter__ 方法):
已經(jīng)可迭代了,接下來讓它成為迭代器
我們可以對它用 for 循環(huán)了:
但是,這種寫法有問題,慢慢看來:
我們要輸出一個 [1, 2, 3, 4, 5] 與它自己的笛卡爾積(不知道啥是笛卡爾積的看這里https://baike.baidu.com/item/%E7%AC%9B%E5%8D%A1%E5%B0%94%E4%B9%98%E7%A7%AF/6323173),即
傳統(tǒng)寫法是
這很簡單。但如果用咱們編的這個 Range,問題就出來了,看輸出:
問題就出在:因為有 return self 的存在,兩個 for 循環(huán)共用了一個迭代器(換而言之,這個 Range 對象只能用一次)!讓這個?Range 對象既保存數(shù)據(jù)又保存狀態(tài)是不行噠!


所以我們需要這里給出兩種解決方案:
(一)自定義專門的迭代器
最本質(zhì)的問題就是 __iter__ 方法里的 return self,我們再定義一個迭代器:
為了要滿足第三條規(guī)則,即迭代器應(yīng)該也是可迭代的,上面也說了,大部分就是返回自己,所以再給 RangeIterator 加上 __iter__ 方法:
這樣,Range 對象就可以多次使用了,但?RangeIterator 作為迭代器本來就只能使用一次。
至此,一個迭代器已經(jīng)實現(xiàn)了。
(二)使用生成器
我們下一章詳細(xì)討論生成器,這里不理解沒關(guān)系(挖坑*1)
我們將 __iter__ 方法變成一個生成器函數(shù):
也能達(dá)到效果,且更加簡潔,推薦使用這種方法。
至于我們剛才討論的第二種情況,實際使用中很少使用,我們就不再贅述。
END

這算是給之前的一章填坑了...
參考資料:
https://docs.python.org/3.8/tutorial/classes.html
https://docs.python.org/3.8/library/stdtypes.html
https://baike.baidu.com/item/%E7%AC%9B%E5%8D%A1%E5%B0%94%E4%B9%98%E7%A7%AF/6323173