Python中的迭代協議

Python中的迭代協議



前言

Python的特色之一是基於協議實現功能。比如改變一個加號(+)的行爲,在C++中需要操作符重載,在Python中則是重寫__add__方法。爲了描述可迭代對象和迭代器,Python提供了兩個魔法方法,分別是__iter____next__。又爲了支持for...in...行爲,牽扯進了__getitem__

我們先從可迭代對象說起。

Iterable

Python在collections.abc模板中提供了Iterable,用於判斷對象是否可迭代,還提供了Iterator,判斷一個對象是否是迭代器。這裏拿熟知的列表類型試水:

lyst = ["t", "y"]

print(isinstance(lyst, Iterable))  # 輸出:True

print(isinstance(lyst, Iterator))  # 輸出:False

可見list只是一個可迭代對象,而非迭代器。

讓一個對象成爲可迭代對象只需要添加__iter__方法。

class Students(object):
    def __iter__(self):
        pass

if __name__ == "__main__":
    stu = Students()
    print(isinstance(stu, Iterable))  # 輸出:True

儘管上面看來其有些欺詐——如果真的將它迭代會發現解釋器報錯TypeError。但不可否認的是,此時解釋器認爲stu是一個可迭代對象了(涉及到**isinstance()**的處理機制,可見《Python中的abc模塊》),所以isinstance()返回出True。

Iterator

與可迭代對象最大的區別在於,可以對迭代器使用next()方法。認爲,迭代器是特殊的可迭代對象,它需要__iter____next__兩個協議支持。

用之前的欺騙手法做個測試:

class Students(object):
    def __iter__(self):
        pass

    def __next__(self):
        pass

if __name__ == "__main__":
    stu = Students()
    print(isinstance(stu, Iterable))  # 輸出:True
    print(isinstance(stu, Iterator))  # 輸出:True

對象stu是可迭代對象,也是迭代器。打印結果證實了前邊說法。

for…in…

直接在協議函數下面寫pass,讓其充裝可迭代對象、迭代器,這是利用了Python鴨子類型的特點。然而,如果希望對象可以正常的被for...in...遍歷,有些邏輯需要我們手動實現。

先說__iter__方法,它只需要完成一個任務——返回一個迭代器。如果我們不返回迭代器,將引發報錯:

class Students(object):
    def __iter__(self):
        return [0, 1, 2]  # 列表類型是可迭代對象,而不是迭代器

if __name__ == "__main__":
    stu = Students()

    for s in stu:
        print(s)

# 輸出:
Traceback (most recent call last):
  File "xxx", line 50, in <module>
    for s in stu:
TypeError: iter() returned non-iterator of type 'list'

現在試着返回一個生成器(生成器是特殊的迭代器):

class Students(object):
    def __iter__(self):
        return (i for i in range(3))

if __name__ == "__main__":
    stu = Students()

    for s in stu:
        print(s)

# 輸出:
0
1
2

事實上,在for...in...過程中,Python會自動調用iter()函數,也就是說for i in x被轉換成了for i in iter(x)處理。想要繼續運行下去,得先保證iter()函數不會拋錯。


基於前面結論,__iter__不負責邏輯處理,那麼處理邏輯就得寫在__next__方法裏面。像下面這樣:

class Students(object):
    def __init__(self):
        self.items = [1, 2, 3, 4]
        self.index = 0

    def __next__(self):
        if self.index >= len(self.items):
            raise StopIteration
        result = self.items[self.index]
        self.index += 1
        return result

此時還不可以對Students的實例對象使用for遍歷,但可以通過next()訪問內部元素:

print(next(stu))
print(next(stu))
print(next(stu))

# 輸出:
1
2
3

之前也說過了,迭代器需要實現__iter____next__兩個方法,所以我們得添加__iter__。又因爲__iter__的任務是返回一個迭代器,擁有了__iter____next__方法的Students的對象本身就是一個迭代器(似乎有點繞),所以返回自身(self)即可:

class Students(object):
	# ...
    def __iter__(self):
        return self
	# ...

另外需要說明的是,在for...in...遍歷過程中,需要一個終止信號,這個信號就是StopIteration。for…in…遍歷的時候等同於:

while True:
    try:
        print(next(stu))
    except StopIteration:
        break

getitem

__getitem__的作用可見《Python中的切片》for...in...運作的完整流程需要用到這個方法。從問第一個問題開始:

  • 問題1:對象中有__iter__方法嗎?
    • 如果有,調用iter(對象);(此時StopIteration是終止信號,for…in…會自動處理StopIteration)
    • 如果沒有,進入問題2。
  • 問題2:對象中有__getitem__方法嗎?
    • 如果有,從0開始傳入索引值訪問內部元素,之後索引值依次累加:1,2,3,…(此時IndexError是終止信號,for…in…會自動處理IndexError)
    • 如果沒有,拋出異常TypeError
      在這裏插入圖片描述

感謝

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