楔子 在介紹 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é)束。
將元素迭代出來之后,,就開始執(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)的具體邏輯:
當(dāng)創(chuàng)建完迭代器之后,,就正式進(jìn)入 for 循環(huán)了,。所以從 FOR_ITER 開始,進(jìn)入了Python虛擬機(jī)層面上的 for循環(huán),。
我們來看一下 FOR_ITER 指令對(duì)應(yīng)的具體實(shí)現(xiàn):
在將迭代出來的元素壓入運(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),。
可以看到和 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é)碼的層面是向下,也就是偏移量增大的方向,。
所以 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,。 另外我們看到break和continue本質(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 還是要簡單一些的,。 |
|