迭代就是循環的意思,也就是對一個集合中的元素進行循環,從而得到每一個元素。對於我們自定義的類,也可以讓其支持迭代,這就是本文要介紹的特殊成員方法__iter__的作用。用該成員方法可以自定義一個Python迭代器
自定義可迭代的類
可能有的讀者會問,爲什麼不使用列表呢?列表可以獲取列表的長度,然後使用變量i對列表索引進行循環,不照樣可以獲取集合的所有元素嗎,還容易理解。沒錯,使用列表的代碼是容易理解,也很好操作,但這是要付出代價的。列表之所以可以用索引來快速定位其中的任何一個元素,是因爲列表是一下子將所有的數據都裝載的內存中了,而且是一塊連續的內存空間。如果數據量比較小還好說,如果數據量很大的話,會非常消耗內存資源。而迭代就不同,迭代是讀取多少元素,就將多少元素裝載到內存中,不讀取就不裝載。這有點像處理XML的兩種方式:DOM和SAX。DOM是一下子將所有的XML數據都裝載到內存中,所以可以快速定位任何一個元素,但代價是消耗內存,而SAX是順序讀取XML文檔,沒讀到的XML文檔內容是不會裝載到內存中的,所以SAX比較節省內存,但只能從前向後順序讀取XML文檔的內容。
如果在一個類中定義__iter__方法,那麼這個類的實例就是一個迭代器。iter__方法需要返回一個迭代器,所以就返回對象本身即可(也就是self)。當對象沒迭代一次時,就會調用迭代器中的另外一個特殊成員方法__next。該方法需要返回當前迭代的結果。下面讓我們先看一個簡單的例子,在這個例子中,通過自定義迭代器對由星號(*)組成的直角三角形的每一行進行迭代,然後通過for循環進行迭代,輸出一定行數的直角三角形。
# 可無限迭代直角三角形的行
class RightTriangle:
def __init__(self):
# 定義一個變量n,表示當前的行數
self.n = 1
def __next__(self):
# 通過字符串的乘法獲取直角三角形每一行的字符串,每一行字符串的長度是2 * n - 1
result = '*' * (2 * self.n - 1)
# 行數加1
self.n += 1
return result
# 該方法必須返回一個迭代器
def __iter__(self):
return self
rt = RightTriangle()
# 對迭代器進行迭代
for e in rt:
# 限制輸出行的長度不能大於20,否則會無限輸出行
if len(e) > 20:
break;
print(e)
程序運行結果如下圖所示。
將迭代器轉換爲列表
儘管迭代器很好用,但仍然不具備某些功能,例如,通過索引獲取某個元素,進行分片操作。這些操作都是列表的專利,所以在很多時候,需要將迭代器轉換爲列表。但有很多迭代器都是無限迭代的,就像上一節中的斐波那契數列的迭代。因此,在將迭代器轉換爲列表時,需要給迭代器能夠迭代的元素限定一個範圍,否則內存就會溢出了。如果要讓迭代器停止迭代,只需要拋出StopIteration異常即可。通過list函數可以直接將迭代器轉換爲列表。
下面的代碼會將斐波那契數列迭代器通過list函數轉換爲列表。斐波那契數列迭代器限制了最大迭代值不能超過500。
# 將迭代器轉換爲列表
class Fibonacci:
def __init__(self):
self.a = 0
self.b = 1
def __next__(self):
result = self.a
self.a, self.b = self.b, self.a + self.b
# 要想讓迭代停止,需要拋出StopIteration異常
if result > 500: raise StopIteration
return result
def __iter__(self):
return self
fibs1 = Fibonacci()
# 將迭代器轉換爲列表
print(list(fibs1))
fibs2 = Fibonacci()
# 使用for循環對迭代器進行迭代
for fib in fibs2:
print(fib, end = ' ')
程序運行結果如圖下圖所示。
從上面的代碼可以看出,儘管在__next__方法中,當result大於500時拋出了StopIteration異常,但這個異常是在迭代的過程中由系統處理的,並不會在程序中拋出,所以如果要將無限迭代改成有限迭代,可以在適當的時候拋出StopIteration異常。