請繼續關注我 函數
點擊上方藍字關注咱們oop
歡迎關注個人公衆號,志學Python學習
前面的基本運算符加減乘除等運算符內容,咱們就不講了,我以爲最應該講講就是 for 循環運算符這東西,真的是須要咱們去好好探討一下的,記得關注點贊哦,謝謝 spa
在本篇博客中,咱們將討論 Python 中 for 循環的原理3d
咱們將從一組基本例子和它的語法開始,還將討論與 for 循環關聯的 else 代碼塊的用處對象
而後咱們將介紹迭代對象、迭代器和迭代器協議,還會學習如何建立本身的迭代對象和迭代器blog
以後,咱們將討論如何使用迭代對象和迭代器實現 for 循環,以及利用 while 循環經過迭代器協議實現 for 循環邏輯rem
最後,咱們將反編譯一個簡單的 for 循環,並逐步介紹 Python 解釋器在執行 for 循環時執行的指令,以知足你們的好奇心。這些有助於理解 for 循環運行時的內部工做原理字符串
Python的for循環博客
for 語句是 Python 中執行迭代的兩個語句之一,另外一個語句是 while。若是你對 Python 的迭代並非很熟悉的話,Python中的迭代:for、while、break、以及continue語句是一個不錯的切入點
Python 中,for 循環用於遍歷一個迭代對象的全部元素。循環內的語句段會針對迭代對象的每個元素項目都執行一次。暫且能夠將迭代對象想象成一個對象集合,咱們能夠一個個遍歷裏面的元素。咱們將在下一節對迭代器和迭代對象做詳細說明
一個簡單的 for 循環
咱們先從一個簡單 for 循環開始,它遍歷一個字符串列表並打印每個字符串
如你所見,這個循環實際上遍歷了列表中的每個單詞並打印它們。也就是說,在循環的每一次遍歷中,變量 word 都被指定爲列表中的一個元素,而後執行 for 語句中的代碼塊。因爲列表是一個有序的元素序列,因此循環也是以相同的順序遍歷這些元素
帶有 else 子句的 for 循環
Python 中的 for 循環能夠選擇是否關聯一個 else 子句。else 子句中的代碼塊是在 for 循環完成後纔開始執行的,即在迭代對象中的全部元素都遍歷完畢以後。如今咱們看一下如何擴展前面的示例以包含一個 else 條件(子句)
else 子句適用於什麼時候?
你已經注意到,else 子句是在 for 循環完成以後才執行的。那麼 else 代碼塊的意義是什麼呢?for 循環以後的語句不是也是一樣會執行嗎?
咱們不少時候會遇到這樣一種狀況,當知足某種條件時,中途結束 for 循環。且若是這個條件一直未知足,則但願執行另外一組語句。咱們一般使用布爾類型的標記實現,下面是一個例子
調用結果:
而用 else 代碼塊的話,咱們能夠避免使用布爾類型的標記_found_item_。咱們看看如何使用 else 子句重寫上面的方法。注意若是 for 循環中的 break 語句被觸發執行,那麼則會跳過 else 塊
因此 else 代碼塊適用於 for 循環中有 break 語句的狀況,且咱們但願 break 條件沒有被觸發的時候執行一些語句
不然,與 else 關聯的語句只會在 for 循環結束時才執行。本文的最後一節查看反編譯的字節碼時你會看到這一點
for 循環語法
咱們已經看到了一些簡單的例子,接下來以 for 循環的語法結束本節
基本上,對於 iterable 中的每個元素,都會執行 set_of_statements_1,一旦全部的元素都迭代一遍,控制器將跳轉到 else 代碼塊中執行 set_of_statements_2
注意,else 子句是可選的。若是沒有發現 else 子句,循環會在全部元素都遍歷完成後結束,而且控制器會轉向程序以後的語句
可迭代對象與迭代器
可迭代對象
在上一節,咱們使用術語 iterable 來表示循環中被迭代的對象。如今咱們來試着瞭解一下 Python 中的 iterable 對象是什麼
Python 中,一個 iterable 對象指在 for 循環中能夠被迭代的任意對象。這意味着,當這個對象做爲參數傳遞給 iter()方法時應該返回一個迭代器。咱們來看一下 Python 中的一些經常使用的內置迭代的例子
如你所見,當咱們對一個 iterable 對象調用 iter() 時,它會返回一個迭代器對象
迭代器
那麼什麼是迭代器呢?迭代器在 Python 中被定義爲一個表現爲流式數據的對象。基本上,若是咱們將對象傳遞給內置的_next()_ 方法,它應該從與之關聯的流式數據中返回下一個值。一旦全部的元素都遍歷結束,它會拋出一個_*StopIteration* 異常。_next()_方法的後續調用也都會拋出*StopIteration*_ 異常。
咱們用一個列表來試一下
迭代器也是可迭代對象!可是...
有一個頗有趣的事須要記一下,_迭代器_一樣支持(強制要求支持_迭代器協議_)_iter()_ 方法。這意味着咱們能夠對一個迭代器調用_iter()_ 方法並獲取它自身的迭代器對象
所以,咱們能夠在任何指望使用迭代器的地方使用它。好比,for 循環
然而要注意一點,在像 list 這樣的容器對象上調用 iter() 每次都會返回不一樣的迭代器,而在迭代器上調用 iter() 僅僅返回同一個迭代器
因此若是你須要進行屢次迭代,而且用迭代器替換普通容器或可迭代對象,那麼第二次你會看到一個空的容器
對一個列表迭代兩次
請注意,這是按照咱們的指望運行的
對一個列表迭代器迭代兩次
請注意,迭代器在第一次循環的時候就已經結束了,第二次咱們看到的是一個空容器
迭代器協議
前文咱們看到了:
1. 一個可迭代對象,做爲參數傳遞給 iter() 方法時返回一個迭代器。
2. 一個迭代器,
1. 做爲參數傳遞給_next()_方法時返回它的下一個元素或者在全部元素都遍歷結束時拋 出_StopIteration_ 異常
2. 做爲參數傳遞給_iter()_ 方法時返回它自身
迭代協議僅僅只是一種將對象定義爲迭代器的標準方式。咱們已經在前一節看到了這種協議的實際應用。根據協議,迭代器應該定義如下兩個方法:
1. __next__()
1. 每次調用這個方法時,應該返回迭代器的下一個元素。一旦元素都遍歷結束,它應該拋出_StopIteration_ 異常
2. 當咱們調動內置函數_next()_ 時,實際內部調用的是本方法
2. __iter__()
1. 這個方法返回迭代器自身
2. 當咱們調動內置函數_iter()_ 時,實際內部調用的是本方法
本身寫一個迭代器
如今咱們已經知道迭代協議的原理,能夠寫一個本身的迭代器了。咱們先看一個例子,下面咱們建立了一個根據給定範圍和步長的 Range 類
咱們看一下它在 for 循環中是怎麼工做的
注意,Range 類的實例是迭代器也是可迭代對象
本身寫一個可迭代對象
咱們還能夠基於 Range 迭代器另外建立一個可迭代對象。它的做用是每當調用 __iter()__ 方法是返回一個新的迭代器,在這裏,它應該返回一個新的 Range 對象
在 for 循環中使用咱們這個 RangeIterable
for 循環工做原理
如今咱們已經知道什麼是迭代器和可迭代對象,接下來了解一下 for 循環是如何工做的
再看一下前面的例子
當咱們執行上面的代碼塊時,發生瞭如下這些事情:
1. 在 for 語句內部對列表 ["You", "are", "awesome!"] 調用了 iter() 方法,返回結果是一個迭代器
2. 而後對迭代器調用 next() 方法,並將其返回值賦給變量 word
3. 以後,會執行 for 循環中關聯的語句塊。這個例子中是打印 word
4. 在 next() 方法拋出 StopIteration 以前會一直重複執行第 2,3 步
5. 一旦 next() 拋出 StopIteration,控制器會跳轉到 else 子句(若是存在)並執行與 else 關聯的語句塊
注意:若是在步驟 3 中,for 循環語句遇到了 break 語句,則跳過 else 代碼塊
使用 while 語句實現 for 循環邏輯
咱們能夠像下面這樣使用 while 語句實現以前的邏輯
while 循環的行爲實際上與 for 循環相同,上面的代碼會有如下輸出
反編譯 for 循環
在本節,咱們將反編譯 for 循環並逐步說明解釋器在執行 for 循環時的指令。這裏使用_dis_ 模塊來反編譯 for 循環。詳細來講,就是咱們將使用 dis.dis 方法來生成可讀性更高的字節碼
咱們會使用以前一直用的簡單 for 循環示例。接下來將文件寫入文件 for_loop.py
咱們能夠調用 dis.dis 方法得到可讀性高的字節碼。在終端上運行如下命令
反編譯輸出的每列表示如下內容:
1. 第 1 列:代碼行數
2. 第 2 列:若是是跳轉指令,則有 ">>" 符號
3. 第 3 列:以字節爲單位的字節碼偏移量
4. 第 4 列:字節碼指令自己
5. 第 5 列:展現指令的參數。若是括號中有內容,它只是對參數作了更好的可讀性轉化
如今咱們來一步步瀏覽反編譯後的字節碼,並嘗試瞭解實際發生了什麼
1. 第 1 行,即,"for word in [「You」, 「are」, 「awesome!」]:" 轉譯爲:
0 SETUP_LOOP 28 (to 30)
該語句將 for 循環中的代碼塊推送到棧中。這段代碼塊會跨越 28 個字節,達到 "30"
這意味着,若是 for 循環中有 break 語句,那麼控制器將跳轉到偏移位置 "30"。注意當遇到 break 語句時是如何跳過 else 代碼塊的
2 LOAD_CONST 0 ((‘You’, ‘are’, ‘awesome!’))
接下來,列表被推送到棧頂(TOS,以後使用 TOS 表示棧頂或棧頂元素)
4 GET_ITER
該指令實現 "TOS = iter(TOS)"。這表示從列表獲取一個迭代器(當前爲 TOS),而後將迭代器推送給 TOS
6 FOR_ITER 12 (to 20)
該指令獲取 TOS,做爲當前的迭代器, 並調用 next() 方法
若是 next() 方法產生一個值,則將其做爲 TOS 推送到棧,並執行嚇一跳指令 "8 STORE_NAME"
一旦 next() 代表迭代器已經遍歷結束(即拋出 StopIteration 異常),TOS(迭代器)將從棧中彈出,字節碼計數器會增長 12。這表示控制器跳轉到指令 "20 POP_BLOCK"
8 STORE_NAME 0 (word)
這個指令執行了轉換 word = TOS,即,_next()_返回的值被賦給變量_word_
2. 第 1 行,即,_"print(word)"_ 轉譯爲:
10 LOAD_NAME 1 (print)
將可調用方法_print_ 推送到棧中
12 LOAD_NAME 0 (word)
將棧中的_word_做爲參數推送給_print_
14 CALL_FUNCTION 1
調用帶位置參數的函數
像咱們看到的指令那樣,與函數關聯的參數會出如今 TOS 中。在得到可調用象的對(如_print_)以前,會彈出全部遇到的參數
一旦得到可調用對象,則把全部參數傳遞給它並調用
可調用對象執行結束後,把返回值推送到 TOS 中,這裏是 None
16 POP_TOP
TOS(棧頂元素),即將函數的返回值從棧中移除(彈出)
18 JUMP_ABSOLUTE 6
此時字節碼計數器爲 「6」,這表示下一條指令將執行 "6 FOR_ITER"。這是循環遍歷迭代器中元素的方式
注意,一旦迭代器中的元素都遍歷結束,指令 "6 FOR_ITER" 會結束循環並跳轉到 "20 POP_BLOCK"
20 POP_BLOCK
POP_BLOCK 會從代碼塊的棧中移除由 「0 SETUP_LOOP」 設置的代碼塊
3. 注意第 3 行(對應_else_),沒有關聯任何特殊指令。程序控制器會順序執行下一條與_else_ 相關的指令
4. 第 4 行,即,_"print("See you later!__")"_ 轉譯爲:
22 LOAD_NAME 1 (print)
推送與_print_ 相關的可調用方法到棧中
24 LOAD_CONST 1 ('See you later!')
推送可調用函數的參數對象到棧中
26 CALL_FUNCTION 1
可調用函數及其參數會從棧中彈出,而後執行函數並將其返回值推送到 TOS
28 POP_TOP
TOS(棧頂元素),即將函數返回值(這裏是 None)從棧中移除
5. 下面的兩個指令只是簡單的將腳本的返回值(None)加載到棧並返回
30 LOAD_CONST 2 (None)
32 RETURN_VALUE
喔!如今咱們已經瞭解了 for 循環反編譯後的指令。但願這有助於更好地理解 for 循環的工做原理