迭代是數據處理的基石。掃描內存中放不下的數據集時,咱們要找到一種惰性獲取數據
項的方式,即按需一次獲取一個數據項。這就是迭代器模式(Iterator pattern)。python
看個例子正則表達式
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __getitem__(self, index): return self.words[index] def __len__(self): return len(self.words) def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) s = Sentence('"The time has come," the Walrus said,') print(s) for world in s: print(world)
解釋器須要迭代對象 x 時,會自動調用 iter(x)。dom
內置的 iter 函數有如下做用。ssh
Python 從可迭代的對象中獲取迭代器函數
next
返回下一個可用的元素,若是沒有元素了,拋出 StopIteration 異常。
iter
返回 self,以便在應該使用可迭代對象的地方使用迭代器,例如在 for 循環中。spa
abc.Iterator 類code
from abc import abstractmethod class Iterator(Iterable): __slots__ = () @abstractmethod def __next__(self): 'Return the next item from the iterator. When exhausted, raise StopIteration' raise StopIteration def __iter__(self): return self @classmethod def __subclasshook__(cls, C): if cls is Iterator: if (any("__next__" in B.__dict__ for B in C.__mro__) and any("__iter__" in B.__dict__ for B in C.__mro__)): return True return NotImplemented
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __getitem__(self, index): return self.words[index] def __len__(self): return len(self.words) def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) # s = Sentence('"The time has come," the Walrus said,') # print(s) # # for world in s: # print(world) ## it就是構造後的迭代器 ## iter(s3) 就是構建迭代器的可迭代對象。 s3 = Sentence('Pig and Pepper') it = iter(s3) print(next(it)) print(next(it)) print(next(it)) # 報錯 StopIteration # print(next(it)) print(list(it)) print(list(iter(s3))) print(list(iter(s3)))
由於迭代器只需 next 和 iter 兩個方法,因此除了調用 next() 方法,以及捕獲 StopIteration 異常以外,沒有辦法檢查是否還有遺留的元素。
此外,也沒有辦法「還原」迭代器。對象
若是想再次迭代,那就要調用 iter(...),傳入以前構建迭代器的可迭代對象。blog
傳入迭代器自己沒用,由於前面說過 Iterator.__iter__ 方法的實現方式是
返回實例自己,因此傳入迭代器沒法還原已經耗盡的迭代器。索引
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) def __iter__(self): return SentenceIterator(self.words) class SentenceIterator: def __init__(self, words): self.words = words self.index = 0 def __next__(self): try: word = self.words[self.index] except IndexError: raise StopIteration() self.index += 1 return word def __iter__(self): return self s = Sentence('"The time has come," the Walrus said,') for word in s: print(word)
❶ 與前一版相比,這裏只多了一個 iter 方法。這一版沒有 getitem 方法,爲
的是明確代表這個類能夠迭代,由於實現了 iter 方法。
❷ 根據可迭代協議,__iter__ 方法實例化並返回一個迭代器。
Sentence 類中,__iter__ 方法調用 SentenceIterator 類的構造方法建立一個迭代器並將其返回。
構建可迭代的對象和迭代器時常常會出現錯誤,緣由是混淆了兩者。
要知道,可迭代的對象有個 iter 方法,每次都實例化一個新的迭代器;
而迭代器要實現 next 方法,返回單個元素,此外還要實現 iter 方法,返回迭代器自己。
所以,迭代器能夠迭代,可是可迭代的對象不是迭代器。
除了 iter 方法以外,你可能還想在 Sentence 類中實現 next 方法,讓
Sentence 實例既是可迭代的對象,也是自身的迭代器。
但是,這種想法很是糟糕。根據有大量 Python 代碼審查經驗的 Alex Martelli 所說,這也是常見的反模式。
生成器函數
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) def __iter__(self): for word in self.words: yield word return s = Sentence('"The time has come," the Walrus said,') for word in s: print(word)
❸ 這個 return 語句不是必要的;這個函數能夠直接「落空」,自動返回。無論有沒有
return 語句,生成器函數都不會拋出 StopIteration 異常,而是在生成徹底部值以後會直接退出。
❹ 不用再單獨定義一個迭代器類!
迭代器實際上是生成器對象,每次調用 iter 方法都會自動建立,由於這裏的 iter 方法是生成器函數。
def gen_123(): # ➊ yield 1 # ➋ yield 2 yield 3 print(gen_123) print(gen_123()) for i in gen_123(): # ➎ print(i) g = gen_123() # ➏ print(next(g)) print(next(g)) print(next(g)) ## 報錯 StopIteration # print(next(g))
❸ 仔細看,gen_123 是函數對象。
❻ 爲了仔細檢查,咱們把生成器對象賦值給 g。
❽ 生成器函數的定義體執行完畢後,生成器對象會拋出 StopIteration 異常。
把生成器傳給next(...) 函數時,生成器函數會向前,執行函數定義體中的下一個 yield 語句,返回產出的值,並在函數定義體的當前位置暫停。
最終,函數的定義體返回時,外層的生成器對象會拋出 StopIteration 異常——這一點與迭代器協議一致。
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) def __iter__(self): # 返回一個迭代器 for match in RE_WORD.finditer(self.text): yield match.group()
re.finditer 函數是 re.findall 函數的惰性版本,返回的不是列表,而是一個生成
器,按需生成 re.MatchObject 實例。
若是有不少匹配,re.finditer 函數能節省大量內存。
咱們要使用這個函數讓第 4 版 Sentence 類變得懶惰,即只在須要時才生成下一個單詞。
❶ 再也不須要 words 列表。
❷ finditer 函數構建一個迭代器,包含 self.text 中匹配 RE_WORD 的單詞,產出
MatchObject 實例。
❸ match.group() 方法從 MatchObject 實例中提取匹配正則表達式的具體文本。
生成器表達式能夠理解爲列表推導的惰性版本:不會迫切地構建列表,而是返回一個生成器,按需惰性生成元素。
def gen_AB(): # ➊ print('start') yield 'A' print('continue') yield 'B' print('end.') res1 = [x * 3 for x in gen_AB()] for i in res1: # ➌ print('-->', i) res2 = (x * 3 for x in gen_AB()) # ➍ print(res2) # ➎ for i in res2: # ➏ print('-->', i)
❷ 列表推導迫切地迭代 gen_AB() 函數生成的生成器對象產出的元素:'A' 和 'B'。注意,下面的輸出是 start、continue 和 end.。
❸ 這個 for 循環迭代列表推導生成的 res1 列表。
❹ 把生成器表達式返回的值賦值給 res2。只需調用 gen_AB() 函數,雖然調用時會返回
一個生成器,可是這裏並不使用。
❺ res2 是一個生成器對象。
❻ 只有 for 循環迭代 res2 時,gen_AB 函數的定義體纔會真正執行。
for 循環每次迭代時會隱式調用 next(res2),前進到 gen_AB 函數中的下一個yield 語句。
注意,gen_AB 函數的輸出與 for 循環中 print 函數的輸出夾雜在一塊兒。
根據個人經驗,選擇使用哪一種句法很容易判斷:若是生成器表達式要分紅多行寫,我傾向
於定義生成器函數,以便提升可讀性。此外,生成器函數有名稱,所以能夠重用。
class ArithmeticProgression: def __init__(self, begin, step, end=None): # ➊ self.begin = begin self.step = step self.end = end # None -> 無窮數列 def __iter__(self): result = type(self.begin + self.step)(self.begin) # ➋ forever = self.end is None # ➌ index = 0 while forever or result < self.end: # ➍ yield result # ➎ index += 1 result = self.begin + self.step * index # ➏
❸ 爲了提升可讀性,咱們建立了 forever 變量,若是 self.end 屬性的值是 None那麼forever 的值是 True,所以生成的是無窮數列。
❹ 這個循環要麼一直執行下去,要麼當 result 大於或等於 self.end 時結束。若是循環退出了,那麼這個函數也隨之退出。
在示例 14-11 中的最後一行,我沒有直接使用 self.step 不斷地增長 result,
而是選擇使用 index 變量,把 self.begin 與 self.step 和 index 的乘積加,計算 result 的各個值,
以此下降處理浮點數時累積效應致錯的風險。
但是,iter 函數還有一個不爲人知的用法:傳入兩個參數,使用常規的函數或任何可調
用的對象建立迭代器。
這樣使用時,第一個參數必須是可調用的對象,用於不斷調用(沒有參數),產出各個值;
第二個值是哨符,這是個標記值,當可調用的對象返回這個值時,觸發迭代器拋出 StopIteration 異常,而不產出哨符。
iter 函數擲骰子,直到擲出 1 點爲止
from random import randint def d6(): return randint(1, 6) d6_iter = iter(d6, 1) print(d6_iter) for roll in d6_iter: print(roll)