Python 迭代過程淺析

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

主要步驟:

  1. a 傳遞給內置函數 iter ,得到返回值 b
  2. 迭代開始。
  3. b 傳遞給內置函數 next ,得到返回值,賦值給循環變量 i
  4. 執行循環體語句,此處爲 print(i)
  5. 重複步驟2 3,直到 next 函數拋出 StopIteration 異常。
  6. 迭代結束。

內置函數 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__ 方案,這不在本文的討論範圍內。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章