Python 迭代過程淺析
mywang88
2019-07-17
簡介
本文嘗試淺析 Python 的迭代上下文的內部過程。
Python 版本爲 3.7 。
示例
迭代上下文
Python 中最常見的迭代上下文是 for 循環語句:
for i in a:
print(i)
如果這段代碼能夠正常執行,我們就說對象 a
是可迭代的,即 Iterable
。
更爲嚴格的說法是,((變量 a
所引用的對象)所屬於的類)的實例對象是可迭代的,括號用來幫助斷句。
簡化的內部過程
爲了更好地理解迭代過程,參考下列代碼:
b = iter(a)
while True:
try:
i = next(b)
print(i)
except StopIteration:
break
主要步驟:
- 將
a
傳遞給內置函數iter
,得到返回值b
。 - 迭代開始。
- 將
b
傳遞給內置函數next
,得到返回值,賦值給循環變量i
。 - 執行循環體語句,此處爲
print(i)
。 - 重複步驟2 3,直到
next
函數拋出StopIteration
異常。 - 迭代結束。
內置函數 iter
會調用 a
的 __iter__
方法,內置函數 next
會調用 b
的 __next__
方法。
不難發現,對象 a
的 __iter__
方法,以及對象 b
的 __next__
方法是一個對象可以被用來迭代的關鍵,而迭代過程的具體邏輯也由這兩個方法定義。
需要補充的是,在 2.2 之後的 Python 版本中,應用了“新式類”的概念。在內置運算的上下文中,對一個對象的內置屬性的引用,會跳過該對象本身的命名空間,直接在對象所屬於的類(繼承樹)中進行查找。
構造可迭代對象
依據上一節的描述,我們可以自己構造一個可迭代對象:
class B:
def __next__(self):
return 'Still me'
class A:
def __iter__(self):
return B()
a = A()
for i in a:
print(i)
運行代碼將不斷打印 Still me
。
更簡化的形式:
class B:
__next__ = lambda self: 'Still me'
class A:
__iter__ = B
我們只需要滿足 __iter__
與 __next__
引用的對象是可調用的即可。
如果一個對象的 __iter__
方法返回其本身,且定義了 __next__
方法,那麼它是一個“迭代器”對象。篇幅所限,本文只給出一個極簡的示例:
class A:
__iter__ = lambda self: self
__next__ = lambda self: 'Still me'
本文中頻繁使用 lambda
語句,是爲了縮短篇幅,同時強調“可調用”這個概念。
更嚴格的內部過程
作爲補充,給出以下示例:
b = a.__class__.__iter__(a)
while True:
try:
i = b.__class__.__next__(b)
print(i)
except StopIteration:
break
當對象 a
被用來迭代時,Python 內部並不會以 a.__iter__
的形式來引用對象 __iter__
屬性,而是調用內置函數 iter(a)
。
這樣一來,無論對象 a
本身是否有定義 __iter__
屬性,Python 都會直接跳過它,去引用它所屬於的類的 __iter__
屬性。如果 a
所屬於的類沒有定義 __iter__
屬性,Python 繼續向上搜索類繼承樹,直到成功或失敗。實際上,這正式 Python 的“新式類”的引用策略。
同理,對於 __next__
屬性,Python 執行相同的處理過程。
爲了更好幫助理解,筆者畫了一張示意圖:
最後,需要補充的是,當 Python 無法 __iter__
與 __next__
的機制來進行迭代的時候,會檢查備選的 __getitem__
方案,這不在本文的討論範圍內。