一文帶你解密Python迭代器的實現(xiàn)原理
可迭代對象與迭代器
Python 一切皆對象,類型對象定義了哪些操作,決定了實例對象擁有哪些行為。
比如類型對象如果定義了 __iter__,那么其實例對象便被稱為可迭代對象(iterable),像字符串、元組、列表、字典、集合等等都是可迭代對象。而整數(shù)、浮點數(shù),由于其類型對象沒有定義 __iter__,所以它們不是可迭代對象。
from?typing?import?Iterable print( ????isinstance("",?Iterable), ????isinstance((),?Iterable), ????isinstance([],?Iterable), ????isinstance({},?Iterable), ????isinstance(set(),?Iterable), )??#?True?True?True?True?True print( ????isinstance(0,?Iterable), ????isinstance(0.0,?Iterable), )??#?False?False
可迭代對象的一大特點就是它可以使用 for 循環(huán)進行遍歷,但是能被 for 循環(huán)遍歷的則不一定是可迭代對象。我們舉個例子:
class?A: ????def?__getitem__(self,?item): ????????return?f"參數(shù)item:?{item}" a?=?A() # 內(nèi)部定義了?__getitem__ # 首先可以讓實例對象像字典一樣訪問屬性 print(a["name"])??#?參數(shù)item:?name print(a["satori"])??#?參數(shù)item:?satori #?此外還可以像可迭代對象一樣被 for 循環(huán) #?循環(huán)的時候會自動給 item 傳值,0?1?2?3... #?如果內(nèi)部出現(xiàn)了 StopIteration,循環(huán)結(jié)束 #?否則會一直循環(huán)下去。這里我們手動 break for?idx,?val?in?enumerate(a): ????print(val) ????if?idx?==?5: ????????break """ 參數(shù)item:?0 參數(shù)item:?1 參數(shù)item:?2 參數(shù)item:?3 參數(shù)item:?4 參數(shù)item:?5 """
所以實現(xiàn)了__getitem__的類的實例,也是可以被for循環(huán)的,但它并不是可迭代對象。
from?typing?import?Iterable print(isinstance(a,?Iterable))??#?False
總之判斷一個對象是否是可迭代對象,就看它的類型對象有沒有實現(xiàn) __iter__。
可迭代對象我們知道了,那什么是迭代器呢?很簡單,調(diào)用可迭代對象的 __iter__ 方法,得到的就是迭代器。
迭代器的創(chuàng)建
不同類型的對象,都有自己的迭代器,舉個例子。
lst?=?[1,?2,?3] # 底層調(diào)用的其實是 list.__iter__(lst) # 從 C 的角度上看,就是 PyList_Type.tp_iter(lst) it?=?lst.__iter__() print(it)??#?<list_iterator?object?at?0x000001DC6E898640> print( ????str.__iter__("") )??#?<str_iterator?object?at?0x000001DC911B8070> print( ????tuple.__iter__(()) )??#?<tuple_iterator?object?at?0x000001DC911B8070>
迭代器也是可迭代對象,只不過迭代器內(nèi)部的 __iter__ 返回的還是它本身。當(dāng)然啦,在創(chuàng)建迭代器的時候,我們更常用內(nèi)置函數(shù) iter。
lst?=?[1,?2,?3] #?等價于?type(lst).__iter__(lst) it?=?iter(lst)
但是 iter 函數(shù)還有一個鮮為人知的用法,我們來看一下:
val?=?0 def?foo(): ????global?val ????val?+=?1 ????return?val #?iter 可以接收一個參數(shù):?iter(可迭代對象) #?iter 也可以接收兩個參數(shù):?iter(可調(diào)用對象,?value) for?i?in?iter(foo,?5): ????print(i) """ 1 2 3 4 """
如果接收的是兩個參數(shù),那么第一個參數(shù)一定是 callable。進行迭代的時候,會不停地調(diào)用接收的可調(diào)用對象,每次迭代出來的值便是 callable 的返回值。當(dāng)返回值等于傳遞第二個參數(shù) value(在底層被稱為哨兵)時,終止迭代。我們看一下 iter 函數(shù)的底層實現(xiàn)。
static?PyObject?* builtin_iter(PyObject?*self,? ????PyObject?*const?*args,?Py_ssize_t?nargs) { ????PyObject?*v; ?? ????//?iter?函數(shù)要么接收一個參數(shù),?要么接收兩個參數(shù) ????if?(!_PyArg_CheckPositional("iter",?nargs,?1,?2)) ????????return?NULL; ????v?=?args[0]; ????//?如果接收一個參數(shù) ????//?那么直接使用?PyObject_GetIter?獲取對應(yīng)的迭代器即可 ????//?可迭代對象的類型不同,那么得到的迭代器也不同 ????if?(nargs?==?1) ????????return?PyObject_GetIter(v); ????//?如果接收的不是一個參數(shù),?那么一定是兩個參數(shù) ????//?如果是兩個參數(shù),?那么第一個參數(shù)一定是可調(diào)用對象 ????if?(!PyCallable_Check(v))?{ ????????PyErr_SetString(PyExc_TypeError, ????????????????????????"iter(v,?w):?v?must?be?callable"); ????????return?NULL; ????} ????//?獲取value(哨兵) ????PyObject?*sentinel?=?args[1]; ????//調(diào)用PyCallIter_New ????//得到?calliterobject?對象 ????/* ????該對象位于?Objects/iterobject.c?中 ????*/ ????return?PyCallIter_New(v,?sentinel); }
以上就是 iter 函數(shù)的內(nèi)部邏輯,既可以接收一個參數(shù),也可以接收兩個參數(shù)。這里我們只看接收一個可迭代對象的情況,所以核心就在于 PyObject_GetIter,它是根據(jù)可迭代對象生成迭代器的關(guān)鍵,我們來看一下它的邏輯是怎么樣的?該函數(shù)定義在 Objects/abstract.c 中。
PyObject?* PyObject_GetIter(PyObject?*o) {?? ????//?獲取可迭代對象的類型對象 ????PyTypeObject?*t?=?Py_TYPE(o); ????//?我們說類型對象定義的操作,決定了實例對象的行為 ????//?實例對象調(diào)用的那些方法都是定義在類型對象里面的 ????//?所以obj.func()本質(zhì)上就是type(obj).func(obj)的語法糖 ????getiterfunc?f; ????//?所以這里是獲取類型對象的?tp_iter?成員 ????//?也就是?Python?中的?__iter__ ????f?=?t->tp_iter; ????//?如果?f?為?NULL ????//?說明該類型對象內(nèi)部的tp_iter成員被初始化為NULL ????//?即內(nèi)部沒有定義?__iter__? ????//?像str、tuple、list等類型對象,它們的tp_iter成員都是不為NULL的 ????if?(f?==?NULL)?{ ???????//?如果?tp_iter?為?NULL,那么解釋器會退而求其次 ???????//?檢測該類型對象中是否定義了?__getitem__ ???????//?如果定義了,那么直接調(diào)用PySeqIter_New ???????//?得到一個seqiterobject對象 ???????//?下面的PySequence_Check負(fù)責(zé)檢測類型對象是否實現(xiàn)了__getitem__ ????????if?(PySequence_Check(o)) ????????????return?PySeqIter_New(o); ????????//?走到這里說明該類型對象既沒有__iter__、也沒有__getitem__ ????????//?因此它的實例對象不具備可迭代的性質(zhì),于是拋出異常 ????????return?type_error("'%.200s'?object?is?not?iterable",?o); ????} ????else?{ ????????//?否則說明定義了__iter__,于是直接進行調(diào)用 ????????//?Py_TYPE(o)->tp_iter(o)?返回對應(yīng)的迭代器 ????????PyObject?*res?=?(*f)(o); ????????//?但如果返回值res不為NULL、并且還不是迭代器 ????????//?證明?__iter__?的返回值有問題,于是拋出異常 ????????if?(res?!=?NULL?&&?!PyIter_Check(res))?{ ????????????PyErr_Format(PyExc_TypeError, ?????????????????????????"iter()?returned?non-iterator?" ?????????????????????????"of?type?'%.100s'", ?????????????????????????Py_TYPE(res)->tp_name); ????????????Py_DECREF(res); ????????????res?=?NULL; ????????} ????????//?返回?res ????????return?res; ????} }
所以我們看到這便是 iter 函數(shù)的底層實現(xiàn),并且當(dāng)類型對象內(nèi)部沒有定義 __iter__ 時,解釋器會退而求其次檢測內(nèi)部是否定義了 __getitem__。
因此以上就是迭代器的創(chuàng)建過程,每個可迭代對象都有自己的迭代器,而迭代器本質(zhì)上只是對原始數(shù)據(jù)的一層封裝罷了。
迭代器的底層結(jié)構(gòu)
由于迭代器的種類非常多,字符串、元組、列表等等,都有自己的迭代器,這里就不一一介紹了。我們就以列表的迭代器為例,看看迭代器在底層的結(jié)構(gòu)是怎么樣的。
typedef?struct?{ ????PyObject_HEAD ????Py_ssize_t?it_index; ????//指向創(chuàng)建該迭代器的列表 ????PyListObject?*it_seq; }?listiterobject;
顯然對于列表而言,迭代器就是在其之上進行了一層簡單的封裝,所謂元素迭代本質(zhì)上還是基于索引,并且我們每迭代一次,索引就自增 1。一旦出現(xiàn)索引越界,就將 it_seq 設(shè)置為 NULL,表示迭代器迭代完畢。
我們實際演示一下:
from?ctypes?import?* class?PyObject(Structure): ????_fields_?=?[ ????????("ob_refcnt",?c_ssize_t), ????????("ob_size",?c_void_p) ????] class?ListIterObject(PyObject): ????_fields_?=?[ ????????("it_index",?c_ssize_t), ????????("it_seq",?POINTER(PyObject)) ????] it?=?iter([1,?2,?3]) it_obj?=?ListIterObject.from_address(id(it)) #?初始的時候,索引為0 print(it_obj.it_index)??#?0 #?進行迭代 next(it) #?索引自增1,此時it_index等于1 print(it_obj.it_index)??#?1 #?再次迭代 next(it) #?此時it_index等于2 print(it_obj.it_index)??#?2 #?再次迭代 next(it) #?此時it_index等于3 print(it_obj.it_index)??#?3
當(dāng) it_index 為 3 的時候,如果再次迭代,那么底層發(fā)現(xiàn) it_index 已超過最大索引,就知道迭代器已經(jīng)迭代完畢了。然后會將 it_seq 設(shè)置為 NULL,并拋出 StopIteration。如果是 for 循環(huán),那么會自動捕獲此異常,然后停止循環(huán)。
所以這就是迭代器,真的沒有想象中的那么神秘,甚至在知道它的實現(xiàn)原理之后,還覺得有點 low。
就是將原始的數(shù)據(jù)包了一層,加了一個索引而已。所謂的迭代仍然是基于索引來做的,并且每迭代一次,索引自增 1。當(dāng)索引超出范圍時,證明迭代完畢了,于是將 it_seq 設(shè)置為 NULL,拋出 StopIteration。
元素迭代的具體過程
我們知道在迭代元素的時候,可以通過 next 內(nèi)置函數(shù),當(dāng)然它本質(zhì)上也是調(diào)用了對象的 __next__ 方法。
static?PyObject?* builtin_next(PyObject?*self,? ????PyObject?*const?*args,?Py_ssize_t?nargs) { ????PyObject?*it,?*res; ?? ????//?同樣接收一個參數(shù)或者兩個參數(shù) ????//?因為調(diào)用next函數(shù)時,可以傳入一個默認(rèn)值 ????//?當(dāng)?shù)鳑]有元素可以迭代的時候,會返回指定的默認(rèn)值 ????if?(!_PyArg_CheckPositional("next",?nargs,?1,?2)) ????????return?NULL; ????it?=?args[0]; ????//?第一個參數(shù)必須是一個迭代器 ????if?(!PyIter_Check(it))?{ ????????//?否則的話,?拋出TypeError ????????//?表示第一個參數(shù)傳遞的不是一個迭代器 ????????PyErr_Format(PyExc_TypeError, ????????????"'%.200s'?object?is?not?an?iterator", ????????????it->ob_type->tp_name); ????????return?NULL; ????}? ????//?it->ob_type?表示獲取類型對象,也就是該迭代器的類型 ????//?可能是列表的迭代器、元組的迭代器、字符串的迭代器等等 ????//?具體是哪一種不重要,因為實現(xiàn)了多態(tài) ????//?然后再獲取?tp_iternext?成員,相當(dāng)于__next__ ????//?拿到函數(shù)指針之后,傳入迭代器進行調(diào)用 ????res?=?(*it->ob_type->tp_iternext)(it); ???? ????//?如果?res?不為?NULL,?那么證明迭代到值了,?直接返回 ????if?(res?!=?NULL)?{ ????????return?res; ????}?else?if?(nargs?>?1)?{ ????????//?否則的話,說明?res?==?NULL, ????????//?這意味著迭代完畢了,或者程序出錯了 ????????//?然后看?nargs?是否大于1,?如果大于1,?說明設(shè)置了默認(rèn)值 ????????PyObject?*def?=?args[1]; ????????//?如果出現(xiàn)異常 ????????if?(PyErr_Occurred())?{ ???????????//?那么就看該異常的種類 ???????????//?能否匹配?StopIteration ????????????if(!PyErr_ExceptionMatches(PyExc_StopIteration)) ????????????//?如果不是,說明程序的邏輯有問題 ????????????//?于是直接return?NULL,結(jié)束執(zhí)行 ????????????//?然后在?Python?里面我們會看到打印到stderr中的異常信息 ????????????????return?NULL; ????????????//?如果是?StopIteration,證明迭代完畢了 ????????????//?但我們設(shè)置了默認(rèn)值,那么就應(yīng)該返回默認(rèn)值 ????????????//?而不應(yīng)該拋出?StopIteration,于是將異?;厮輻=o清空 ????????????PyErr_Clear(); ????????} ????????//?然后增加默認(rèn)值的引用計數(shù),?并返回 ????????Py_INCREF(def); ????????return?def; ????}?else?if?(PyErr_Occurred())?{ ????????//走到這里說明程序出異常了,并且沒有指定默認(rèn)值 ????????//那么這種情況,不管什么異常都直接拋出 ????????return?NULL; ????}?else?{ ????????//?都不是的話,仍是直接拋出?StopIteration ????????PyErr_SetNone(PyExc_StopIteration); ????????return?NULL; ????} }
以上就是next函數(shù)的背后邏輯,實際上還是調(diào)用了迭代器的__next__方法。
lst?=?[1,?2,?3] it?=?iter(lst) #?然后迭代,等價于next(it) print(type(it).__next__(it))??#?1 print(type(it).__next__(it))??#?2 print(type(it).__next__(it))??#?3 #?但是next可以指定默認(rèn)值 #?如果不指定默認(rèn)值,或者還是type(it).__next__(it) #?那么就會報錯,會拋出StopIteration print(next(it,?666))??#?666
以上就是元素的迭代,但是我們知道內(nèi)置函數(shù)next要更強大一些,因為它還可以指定一個默認(rèn)值。當(dāng)然在不指定默認(rèn)值的情況下,next(it) 和 type(it).__next__(it) 是等價的。
我們?nèi)砸粤斜淼牡鳛槔?,看?__next__ 的具體實現(xiàn)。
static?PyObject?* listiter_next(listiterobject?*it) { ????PyListObject?*seq;??//列表 ????PyObject?*item;?????//元素 ???? ????assert(it?!=?NULL); ????//拿到具體對應(yīng)的列表 ????seq?=?it->it_seq; ????//如果seq為NULL,證明迭代器已經(jīng)迭代完畢 ????//否則它不會為NULL ????if?(seq?==?NULL) ????????return?NULL; ????assert(PyList_Check(seq)); ????//如果索引小于列表的長度,證明尚未迭代完畢 ????if?(it->it_index?<?PyList_GET_SIZE(seq))?{ ????? ?//通過索引獲取指定元素 ????????item?=?PyList_GET_ITEM(seq,?it->it_index); ?????? //it_index自增1 ????????++it->it_index; ?????? //增加引用計數(shù)后返回 ????????Py_INCREF(item); ????????return?item; ????} ????//否則的話,說明此次索引正好已經(jīng)超出最大范圍 ????//意味著迭代完畢了,將it_seq設(shè)置為NULL ????//并減少它的引用計數(shù),然后返回 ????it->it_seq?=?NULL; ????Py_DECREF(seq); ????return?NULL; }
顯然這和我們之前分析的是一樣的,以上我們就以列表為例,考察了迭代器的實現(xiàn)原理和元素迭代的具體過程。當(dāng)然其它對象也有自己的迭代器,有興趣可以自己看一看。
小結(jié)
到此,我們再次體會到了 Python 的設(shè)計哲學(xué),通過 PyObject * 和 ob_type 實現(xiàn)了多態(tài)。原因就在于它們接收的不是對象本身,而是對象的 PyObject * 泛型指針。
不管變量 obj 指向什么樣的可迭代對象,都可以交給 iter 函數(shù),會調(diào)用類型對象內(nèi)部的 __iter__,底層是 tp_iter,得到對應(yīng)的迭代器。不管變量 it 指向什么樣的迭代器,都可以交給 next 函數(shù)進行迭代,會調(diào)用迭代器的類型對象的 __next__,底層是 tp_iternext,將值迭代出來。
至于__iter__和__next__本身,每個迭代器都會有,我們這里只以列表的迭代器為例。
到此這篇關(guān)于一文帶你解密Python迭代器的實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Python迭代器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python實現(xiàn)獲取內(nèi)網(wǎng)IP地址的方法總結(jié)
這篇文章主要為大家詳細介紹了五種利用Python語言實現(xiàn)獲取內(nèi)網(wǎng)IP地址的方法,文中的示例代碼講解詳細,具有一定的參考價值,需要的可以了解一下2023-03-03Python依賴管理及打包工具Poetry使用規(guī)范
這篇文章主要為大家介紹了Python依賴管理及打包工具Poetry的依賴規(guī)范,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2021-09-09python中的os.mkdir和os.makedirs的使用區(qū)別及如何查看某個模塊中的某些字母開頭的屬性方法
這篇文章主要介紹了python中的os.mkdir和os.makedirs的使用區(qū)別及如何查看某個模塊中的某些字母開頭的屬性方法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03python教程之利用pyautogui圖形自動化擊敗重復(fù)性辦公任務(wù)
在使用Python做腳本的話,有兩個庫可以使用,一個為PyUserInput庫,另一個為pyautogui庫,就本人而言更喜歡使用pyautogui庫,該庫功能多,使用便利,下面這篇文章主要給大家介紹了關(guān)于python教程之利用pyautogui圖形自動化擊敗重復(fù)性辦公任務(wù)的相關(guān)資料,需要的朋友可以參考下2022-03-03Python的Tornado框架的異步任務(wù)與AsyncHTTPClient
Tornado的奧義就在于異步處理來提高單線程的Python程序執(zhí)行性能,這里我們就來詳解Python的Tornado框架的異步任務(wù)與AsyncHTTPClient,需要的朋友可以參考下2016-06-06python監(jiān)測當(dāng)前聯(lián)網(wǎng)狀態(tài)并連接的實例
今天小編就為大家分享一篇python監(jiān)測當(dāng)前聯(lián)網(wǎng)狀態(tài)并連接的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-12-12python 循環(huán)數(shù)據(jù)賦值實例
今天小編就為大家分享一篇python 循環(huán)數(shù)據(jù)賦值實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12詳解python3實現(xiàn)的web端json通信協(xié)議
本篇文章主要介紹了python3實現(xiàn)的web端json通信協(xié)議,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2016-12-12