概述
迭代是數據處理的基石,掃描內存中放不下的數據時,我們需要找到一種惰性獲取數據項的方式,即按需一次獲取一個數據項。這就是迭代器模式。
在python中,所有集合都可以迭代。在python語言內部,迭代器用於支持:
(1)for循環
(2)構建和擴展集合類型
(3)逐行遍歷文本文件
(4)列表推導,字典推導和集合推導
(5)元組拆包
(6)調用函數時,用*拆包
python沒有宏,因此為了抽象出迭代器模式,需要改動語言本身,為此加入了yield用於構建生成器。所有生成器都是迭代器,反之則不是。迭代器用於從集合中取出元素,而生成器用於憑空產生元素。
iter函數
解釋器需要迭代對象x時,會主動調用iter(x)。內置iter函數有以下作用:
(1)檢查對象是否實現了__iter__方法,如果實現了就調用它,獲取一個迭代器。
(2)如果沒有實現__iter__方法,但是實現了_getitem__方法,python會創建一個迭代器,嘗試按順序(從索引0開始)獲取元素。
(3)如果嘗試失敗,python拋出TypeError異常,通常會提示對象不可迭代。
任何序列都可以迭代,其原因是,它們都實現了__getitem__方法。其實,標准的序列也都實現了__iter__方法。
實現了__iter__方法,就認為對象是可以迭代的,此時,不需要創建子類,也不用注冊,因為abc.Iterable類實現了__subclasshook__方法。
class Foo: def __iter__(self): pass from collections import abc print(issubclass(Foo, abc.Iterable)) f = Foo() print(isinstance(f, abc.Iterable)) #結果 True True
而只實現了__getitem__方法的類,雖然可以迭代,但是無法通過issubclass測驗。
迭代對象之前顯示檢查對象是否可以迭代或許沒有必要,畢竟嘗試迭代不可迭代的對象時,會拋出對象不可迭代的異常。如果除了拋出異常還有進一步處理,可以使用try/catch塊。
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) #結果 <callable_iterator object at 0x00000264FD3C54A8> 4 4 2 5 5 5
可迭代對象
可迭代的對象:使用iter內置函數可以獲取迭代器的對象。如果對象實現了能返回迭代器的__iter__函數,那么對象就是可以迭代的。序列都可以迭代;實現了__getitem__方法,而且參數是從零開始的索引,這種對象也可以迭代。
可迭代對象與迭代器的關系是:python從可迭代對象中獲取迭代器。
迭代一個字符串,'ABC'是可迭代對象,for循環背后是有迭代器的:
s = 'ABC' for char in s: print(char)
如果不用for循環:
s = 'ABC' it = iter(s) #使用可迭代對象構建迭代器it while True: try: print(next(it)) #不斷在迭代器上調用next函數,獲取下一個字符 except StopIteration: #沒有字符會拋出StopIteration del it #廢棄迭代器對象 break
StopIteration異常表明迭代器到頭了。python語言內部處理for循環和其他迭代上下文(概述中的那些)中的StopIteration異常。
標准迭代器接口有兩個方法:
__next__ //返回下一個可用元素,如果沒有下一個了,拋出StopIteration異常
__iter__ //返回self,以便在應該使用可迭代對象的地方使用迭代器。
這個接口在collections.abc.Iterator抽象基類中制定。這個類定義了__next__抽象方法,而且繼承自Iterable類;__iter__抽象方法則在Iterable類中定義:

因為迭代器只需 __next__ 和 __iter__ 兩個方法,所以除了調用 next() 方法,以及捕獲 StopIteration 異常之外,沒有辦法檢查是否還有遺留的元素。此外,也沒有辦法“還原”迭代器。如果想再次迭代,那就要調用 iter(...),傳入之前構建迭代器的可迭代對象。傳入迭代器本身沒用,因為前面說過 Iterator.__iter__ 方法的實現方式是返回實例本身,所以傳入迭代器無法還原已經耗盡的迭代器。
再次給出迭代器定義:
迭代器:實現了無參數的 __next__ 方法,返回序列中的下一個元素;如果沒有元素了,那么拋出 StopIteration 異常。Python 中的迭代器還實現了 __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 #SentenceIterator實例引用單詞列表 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
對這個示例來說,其實沒必要在SentenceIterator類中實現__iter__方法,不過迭代器實現了__iter__和__next__方法后可以通過issubclass(SentenceInterator,abc.Interator)測試。如果讓它繼承abc.Iterator類,那么它會繼承abc.Iterator.__iter__方法。
一定不能將可迭代對象和迭代器混淆,可迭代對象有一個__iter__方法,每次都實例化一個新的迭代器,而迭代器要實現__next__方法返回單個元素,此外還要實現__iter__方法,返回迭代器本身。因此,迭代器可以迭代,可迭代對象不是迭代器。
生成器函數
使用生成器函數代替SentenceIterator類:
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 #產出當前word return #不是必要的
這比前一個迭代器模式少了一個SentenceIterator類。
生成器工作原理
python函數定義體中有yield關鍵字,即該函數是生成器函數(上面的__iter__函數就是生成器函數),調用生成器函數時,會返回一個生成器對象。簡單例子:
def gen_abc(): #函數體中包含yield關鍵字,就是生成器函數 yield 'a' yield 'b' yield 'c' print(gen_abc) print(gen_abc()) for i in gen_abc(): #調用時,會生成傳遞給yield關鍵字的表達式的值 print(i) #結果 <function gen_abc at 0x0000015D77F57F28> #gen_abc是函數對象 <generator object gen_abc at 0x0000015D7F513A98> #調用函數返回一個生成器對象 a b c
調用next()查看:
g = gen_abc() print(next(g)) #g是迭代器,調用next(g)獲取yield生成的下一個元素 print(next(g)) print(next(g)) print(next(g)) #結果 a b c StopIteration #元素已生成完畢,拋出異常
惰性實現Sentence類
惰性求值和及早求值是編程語言理論方面的技術術語。無論是上述使用迭代器模式或者生成器函數實現的Sentence類都不具有惰性,因為__init__方法急迫得構建好了文本單詞列表,然后將其綁定到self.words屬性上。列表耗費了大量內存,如果只需要迭代前幾個單詞,那么大多數工作都是白費力氣。
re.finditer是re.findall的惰性版本,返回的不是列表,而是一個生成器,在一些情況下能夠節省大量內存,只在需要的時候產生元素:
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()
生成器表達式
生成器表達式可以理解為列表推導式的惰性版本,不會急迫得構建列表,而是返回一個生成器,按需惰性生成元素。
對比列表推導式與生成器表達式:
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) #列表推導式結果 start continue end #列表推導式迫切得迭代生成器對象產生的元素,因此直接先輸出了start,continue,end --> AAA --> BBB #for循環迭代輸出生成的列表 res2 = (x*3 for x in gen_AB()) #生成器表達式 for i in res2: print('-->', i) #生成器表達式結果 start --> AAA continue --> BBB end #for循環迭代res2時,gen_AB函數體才執行,for循環每次迭代會隱式調用next(),前進到下一個yield語句
同樣可以用生成器表達式實現Sentence類(改動__iter__函數):
def __iter__(self): return (match.group() for match in RE_WORD.finditer(self.text))
這里沒有生成器函數了(沒有yield語句),而是用生成表達式構建生成器,然后將其返回。
生成器函數與生成器表達式各有好處,一般語句復雜時使用生成器函數,簡單時使用生成器表達式。
標准庫中的生成器函數
用於過濾的生成器函數:

用於映射的生成器函數:

合並多個可迭代對象的生成器函數:

把輸入的各個元素擴展成多個輸出元素的生成器函數:

用於重新排列元素的生成器函數:

讀取迭代器,返回單個值的內置函數:

生成器與迭代器的語義對比
三個方面:
1.接口
python迭代器協議定義了兩個方法__next__和__iter__。生成器對象實現了這兩個方法,因此,從這方面來看,所有的生成器都是迭代器。
2.實現方式
生成器這種python語言結構可以用兩種方式編寫:含有yield關鍵字的函數,或者生成器表達式。調用生成器函數或者執行生成器表達式得到的生成器對象屬於語言內部的GeneratorType類型。從這方面看,所有生成器都是迭代器,因為GeneratorType類型的實例實現了迭代器接口。但是卻可以編寫不是生成器的迭代器,方法是實現經典的迭代器模式。
3.概念
在典型的迭代器設計模式中,迭代器用於遍歷集合,從中產生元素。迭代器可能相當復雜,例如遍歷樹狀數據結構。但是,不管典型的迭代器中有多燒邏輯,都是從現有的數據源中讀取值;而且,調用next(it)時,迭代器不能修改從數據源中讀取的 值,只能原封不動地產出值。
而生成器可能無需遍歷集合就能生成值,例如range函數,即便依附集合,生成器不僅能產生集合中的元素,還可能產出派生自元素的其他值。從這方面講,生成器不都是迭代器。
從概念方面來說,不使用生成器對象也能編寫生成器
以上來自《流暢的python》
