久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

《源碼探秘 CPython》53. 流程控制語句 for、while 是怎么實(shí)現(xiàn)的,?

 古明地覺O_o 2022-12-08 發(fā)布于北京

楔子


在介紹 if 語句的時(shí)候,我們已經(jīng)見識(shí)了最基本的控制,,但是我們發(fā)現(xiàn) if 只能向前跳轉(zhuǎn),,不管是哪個(gè)分支,都是通過 JUMP_FORWARD,。而下面介紹的 for,、while 循環(huán),指令是可以回退的,,也就是向后跳轉(zhuǎn),。

另外,在 if 語句的分支中,,無論哪個(gè)分支,,其指令的跳躍距離通常都是當(dāng)前指令與目標(biāo)指令的距離,相當(dāng)于向前跳了多少步,。那么指令回退時(shí),,是不是相當(dāng)于向后跳了多少步呢?帶著疑問,,我們來往下看,。


for


我們來看一個(gè)簡單的 for 循環(huán)的字節(jié)碼。

反編譯之后,,字節(jié)碼指令如下:

我們直接從 10 GET_ITER 開始看起,,首先 for 循環(huán)遍歷一個(gè)對(duì)象的時(shí)候,要滿足后面的對(duì)象是一個(gè)可迭代對(duì)象,,然后會(huì)先調(diào)用這個(gè)對(duì)象的__iter__方法,,把它變成一個(gè)迭代器。再不斷地調(diào)用這個(gè)迭代器的__next__方法,,一步一步將里面的值全部迭代出來,,當(dāng)出現(xiàn)StopIteration異常時(shí),for循環(huán)捕捉,,最后退出。

另外,,我們說 Python 里面是先有值,,后有變量,for 循環(huán)也不例外,。循環(huán)的時(shí)候,,先將 lst 對(duì)應(yīng)的迭代器中的元素迭代出來,然后再讓變量 item 指向,。所以字節(jié)碼中先是 12 FOR_ITER,,然后才是 14 STORE_NAME,。

因此包含10個(gè)元素的迭代器,需要迭代11次才能結(jié)束,。因?yàn)?for 循環(huán)事先是不知道迭代10次就能結(jié)束的,,它需要再迭代一次發(fā)現(xiàn)沒有元素可以迭代、從而拋出StopIteration異常,、再進(jìn)行捕捉,,之后才能結(jié)束。

for 循環(huán)遍歷可迭代對(duì)象時(shí),,會(huì)先拿到對(duì)應(yīng)的迭代器,,那如果遍歷的就是一個(gè)迭代器呢?答案是依舊調(diào)用 __iter__,,只不過由于本身就是一個(gè)迭代器,,所以返回的還是其本身。

將元素迭代出來之后,,就開始執(zhí)行 for 循環(huán)體的邏輯了,。

執(zhí)行完之后,通過 JUMP_ABSOLUTE 跳轉(zhuǎn)到字節(jié)碼偏移量為12,、也就是 FOR_ITER 的位置開始下一次循環(huán),。這里我們發(fā)現(xiàn)它沒有跳到 GET_ITER 那里,所以可以得出結(jié)論,,for 循環(huán)在遍歷的時(shí)候只會(huì)創(chuàng)建一次迭代器,。

下面來看指令對(duì)應(yīng)的具體邏輯:

case TARGET(GET_ITER): {    //在 GET_ITER之前,LOAD_NAME已經(jīng)將 lst 壓入運(yùn)行時(shí)棧    //此處會(huì)從運(yùn)行時(shí)棧的頂端獲取壓入的 lst    PyObject *iterable = TOP();    //調(diào)用PyObject_GetIter,,獲取對(duì)應(yīng)的迭代器    //這個(gè)函數(shù)我們在介紹迭代器的時(shí)候已經(jīng)說過了    PyObject *iter = PyObject_GetIter(iterable);    Py_DECREF(iterable);    //將變量 iter 設(shè)置為新的棧頂元素    SET_TOP(iter);    if (iter == NULL)        goto error;    //指令預(yù)測,,Python 認(rèn)為 GET_ITER 的下一條指令    //很有可能是 FOR_ITER,其次是 CALL_FUNCTION    PREDICT(FOR_ITER);    PREDICT(CALL_FUNCTION);    //continue    DISPATCH();}

當(dāng)創(chuàng)建完迭代器之后,,就正式進(jìn)入 for 循環(huán)了,。所以從 FOR_ITER 開始,進(jìn)入了Python虛擬機(jī)層面上的 for循環(huán),。

源代碼中的 for循環(huán),,在虛擬機(jī)層面也一定對(duì)應(yīng)著一個(gè)相應(yīng)的循環(huán)控制結(jié)構(gòu)。因?yàn)闊o論進(jìn)行怎樣的變換,,都不可能在虛擬機(jī)層面利用順序結(jié)構(gòu)來實(shí)現(xiàn)源碼層面上的循環(huán)結(jié)構(gòu),,這也可以看成是程序的拓?fù)洳蛔冃浴?/p>

我們來看一下 FOR_ITER 指令對(duì)應(yīng)的具體實(shí)現(xiàn):

case TARGET(FOR_ITER): {    // 指令預(yù)測,預(yù)測成功會(huì)直接跳轉(zhuǎn)到此處    PREDICTED(FOR_ITER);    /* 從棧頂獲取 iterator 對(duì)象(指針) */    PyObject *iter = TOP();    //調(diào)用迭代器類型對(duì)象的 tp_iternext方法    //將迭代器內(nèi)的元素迭代出來    PyObject *next = (*iter->ob_type->tp_iternext)(iter);    //如果next不為NULL,,那么將元素壓入運(yùn)行時(shí)棧    //等待被賦值給 for循環(huán)的變量    if (next != NULL) {        PUSH(next);        //依舊是指令預(yù)測        //對(duì)于我們當(dāng)前這個(gè)例子來說,,顯然預(yù)測失敗了        //這里是 STORE_FAST,而例子中是 STORE_NAME        //原因是Python虛擬機(jī)認(rèn)為 for循環(huán)更有可能在局部作用域中出現(xiàn)        //而我們當(dāng)前的for循環(huán)位于全局作用域        PREDICT(STORE_FAST);        PREDICT(UNPACK_SEQUENCE);        //continue        DISPATCH();    }    //tstate指的是線程狀態(tài)對(duì)象,我們會(huì)后面分析    //這里表示如果出現(xiàn)了異常    if (_PyErr_Occurred(tstate)) {        //并且還不是StopIteration        //那么證明執(zhí)行的時(shí)候出錯(cuò)了        if (!_PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) {            goto error;        }        //否則說明是StopIteration,,意味著迭代就結(jié)束了        else if (tstate->c_tracefunc != NULL) {            call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, f);        }        //_PyErr_Clear會(huì)將異?;厮輻G蹇?/span>        //從Python的層面來看,等于是for循環(huán)將StopIteration自動(dòng)捕捉了        _PyErr_Clear(tstate);    }    //走到這里說明循環(huán)結(jié)束了    STACK_SHRINK(1);    Py_DECREF(iter);    JUMPBY(oparg);    PREDICT(POP_BLOCK);    DISPATCH();}

在將迭代出來的元素壓入運(yùn)行時(shí)棧之后(預(yù)測失敗),,會(huì)執(zhí)行 STORE_NAME,。然后虛擬機(jī)將沿著字節(jié)碼指令的順序一條一條地執(zhí)行下去,從而完成輸出的動(dòng)作,。

但是我們知道,,for循環(huán)中肯定會(huì)有指令回退的動(dòng)作。從字節(jié)碼中也看到了,,for循環(huán)遍歷一次之后,,會(huì)再次跳轉(zhuǎn)到FOR_ITER,而跳轉(zhuǎn)所使用的指令就是JUMP_ABSOLUTE,。這個(gè)在介紹 if 的時(shí)候,,我們已經(jīng)說過了,它表示絕對(duì)跳轉(zhuǎn),。

case TARGET(JUMP_ABSOLUTE): {    PREDICTED(JUMP_ABSOLUTE);    //顯然這里的oparg表示字節(jié)碼偏移量    //表示直接跳轉(zhuǎn)到偏移量為oparg的位置上    JUMPTO(oparg);#if FAST_LOOPS    FAST_DISPATCH();#else    DISPATCH();#endif}
#define JUMPTO(x) (next_instr = first_instr + (x) / sizeof(_Py_CODEUNIT))

可以看到和 if 不一樣,,for循環(huán)使用的是絕對(duì)跳轉(zhuǎn)。JUMP_ABSOLUTE 是強(qiáng)制設(shè)置 next_instr 的值,,將 next_instr 設(shè)定到距離f->f_code->co_code開始地址的某一特定偏移量的位置,。這個(gè)偏移量由JUMP_ABSOLUTE的指令參數(shù)決定,所以該參數(shù)就成了 for循環(huán)中指令回退動(dòng)作的最關(guān)鍵的一點(diǎn),。

天下沒有不散的宴席,,隨著迭代的進(jìn)行,for循環(huán)總有退出的那一刻,,而這個(gè)退出的動(dòng)作只能落在 FOR_ITER 的身上,。在 FOR_ITER 指令執(zhí)行的過程中,如果遇到了 StopIteration,,就意味著迭代結(jié)束了,。

這個(gè)結(jié)果將導(dǎo)致Python虛擬機(jī)會(huì)將迭代器從運(yùn)行時(shí)棧中彈出,同時(shí)執(zhí)行一個(gè) JUMPBY 的動(dòng)作,,向前跳躍,,在字節(jié)碼的層面是向下,也就是偏移量增大的方向,。

#define JUMPBY(x)       (next_instr += (x) / sizeof(_Py_CODEUNIT))
case TARGET(FOR_ITER): {    /*  ... ... ...    */ //走到這里說明循環(huán)結(jié)束了    //STACK_SHRINK會(huì)對(duì)棧進(jìn)行收縮    //此處就相當(dāng)于將迭代器(指針)從運(yùn)行時(shí)棧中彈出 STACK_SHRINK(1);    //減少迭代器的引用計(jì)數(shù) Py_DECREF(iter);    //進(jìn)行跳轉(zhuǎn),,此時(shí)采用相對(duì)跳轉(zhuǎn)    //顯然會(huì)跳轉(zhuǎn)到整個(gè)for循環(huán)下面的第一條語句的位置 JUMPBY(oparg); PREDICT(POP_BLOCK); DISPATCH();}

所以 for 循環(huán)結(jié)束時(shí)采用的是相對(duì)跳轉(zhuǎn),進(jìn)入下一次循環(huán)時(shí)采用的是絕對(duì)跳轉(zhuǎn),。

以上就是 for 循環(huán)的原理,從字節(jié)碼的角度去理解它,,是不是別有一番風(fēng)味呢,?


while


看完了 for,,再來看 while 就簡單了。不僅如此,,我們還要分析兩個(gè)關(guān)鍵字:break,、continue,當(dāng)然goto就別想了,。

反編譯之后,,指令如下,和 for有很多是類似的,。

有了 for 循環(huán),,再看 while 循環(huán)就簡單多了,整體邏輯和 for 高度相似,,當(dāng)然里面還結(jié)合了 if,。

另外我們看到breakcontinue本質(zhì)上都是一個(gè)JUMP_ABSOLUTE,都是通過絕對(duì)跳轉(zhuǎn)實(shí)現(xiàn)的,。break 是跳轉(zhuǎn)到 while循環(huán)結(jié)束后的第一條指令,;continue 則是跳轉(zhuǎn)到 while 循環(huán)的開始位置。

然后執(zhí)行一圈之后,,再通過JUMP_ABSOLUTE跳轉(zhuǎn)回去,,顯然此時(shí)的指令參數(shù)和 continue 是一樣的,都是 while 循環(huán)的開始位置,。

當(dāng)循環(huán)條件不滿足的時(shí)候,,指令 POP_JUMP_IF_FALSE 發(fā)現(xiàn)結(jié)果為 False,直接結(jié)束循環(huán),,此時(shí)的指令參數(shù)和 break 是一樣的,,都是跳轉(zhuǎn)到 while 循環(huán)的結(jié)束位置,或者說 while 循環(huán)的下一條指令的開始位置,。

所以 while 事實(shí)上比 for 還是要簡單一些的,。

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多