本篇將介紹Python3中的迭代器與生成器,描述可迭代與迭代器關係,並實現自定義類的迭代器模式。python
Python標準庫中存在着一些可迭代對象,例如:list, tuple, dict, set, str等。
能夠對這些迭代對象,進行for-in等迭代操做,例如:express
for s in "helloworld": print(s)
編譯器若想迭代一個對象a,則會自動調用iter(a)獲取該對象的迭代器(iterator),若是iter(a)拋出異常,則對象a不可迭代。函數
原生函數iter(instance) 能夠判斷某個對象是否可迭代,它的工做流程大概分爲如下3個步驟:spa
根據第一條,咱們自定義類Iter1實現__iter__方法使該類的對象可迭代。code
class Iter1: def __init__(self, text): self.text = text def __iter__(self): return iter(self.text) iter1 = Iter1("hello") for s in iter1: print(s)
Iter1類實現了__iter__方法,經過iter()調用,獲得可迭代對象text的迭代器並返回,實現了迭代器協議,所以能夠經過for-in等方式對該對象進行迭代。
第二條一般都是針對Python中的序列(sequence)而定義,例如list,爲了實現sequence協議,須要實現__getitem__方法。對象
class Iter2: def __init__(self, sequence): self.sequence = sequence def __getitem__(self, item): return self.sequence[item] iter2 = Iter2([1, 2, 3, 4]) for s in iter2: print(s)
實際上,爲了不版本後序改動,Python標準庫中的序列除了實現了__getitem__方法,也實現了__iter__方法,所以咱們在定義序列時也應實現__iter__。
綜上,若是顯示判斷某個對象是否可迭代,應該調用iter(instance)是否拋出異常,由於只實現了__getitem__的序列也是可迭代的(例子中Iter2的對象是可迭代的,但isinstance(iter2, abc.Iterator)返回結果是False)。同時,若是在調用iter後進行迭代操做沒必要顯示判斷,能夠用try/except方式包裝代碼塊。blog
iterable定義ip
任何能夠由原生函數iter獲取到迭代器的對象
任何實現了__iter__方法並返回迭代器的對象
全部的序列(實現了__getitem__)
Python經過獲取到可迭代對象的迭代器(iterator)實現迭代,例如for-in的實現實際上是在內部獲取到了迭代器進行操做。for-in機制能夠理解爲下述代碼:get
s = 'hello' it = iter(s) while (True): try: print(next(it)) except StopIteration: del it break
StopIteration異常將在迭代器耗盡後被拋出,for-in、生成式(comprehension)、元組解壓(tuple unpacking)等迭代操做都會處理並這個異常。generator
迭代器是個迭代值生產工廠,它保存迭代狀態,並經過next()函數產生下一個迭代值。實現迭代器須要實現如下兩個方法:
迭代器實現__iter__,所以全部的迭代器都是可迭代的,下圖展現了iterable和iterator的結構。
實現一個自定義的迭代器模式須要兩個類,分別爲實現了__iter__方法的類和經過__iter__返回的迭代器實例類(實現了__iter__和__next__方法)。下面例子簡單實現了上述功能。
class IterText: def __init__(self, text): self.text = text def __iter__(self): return IteratorText(self.text) class IteratorText: def __init__(self, text): self.text = text self.index = 0 def __iter__(self): return self def __next__(self): try: letter = self.text[self.index] except IndexError: raise StopIteration self.index += 1 return letter text = IterText("hey") for l in text: print(l)
可迭代的IterText實現了__iter__方法,返回了迭代器IteratorText實例。IteratorText實現了__next__方法返回下一個迭代元素直到拋出異常,同時IteratorText實現了__iter__方法返回自身對象用於迭代。
這裏的IterText和IteratorText很容易混淆,若是在IterText中實現了__next__方法並將__iter__中返回自身實例self也能夠實現上述功能,但一般可迭代對象和迭代器應當分開,這樣在可迭代對象中的__iter__中能夠返回不一樣的迭代器對象,使功能獨立。
經過上述文章說明,迭代器經過next()不斷產出下一個元素直到迭代器耗盡,而Python中的生成器能夠理解爲一個更優雅的迭代器(不須要實現__iter__和__next__方法),實現了迭代器協議,它也能夠經過next()產出元素。
Python中的生成器主要分爲兩種類型:
def gen_func(): yield 1 yield 2 yield 3 g = gen_func()
g = (i for i in (1, 2, 3))
咱們能夠利用生成器進行迭代操做:
for e in g: print(e) ## 生成器g已被耗盡,若是須要從新迭代須要從新得到新的生成器對象 g = gen_func() for e in g: print(e)
在迭代器模式章節中,咱們在可迭代IterText中的__iter__返回迭代器IteratorText實例,然而使用生成器的方式會使代碼更加優雅。
class IterText: def __init__(self, text): self.text = text def __iter__(self): for letter in self.text: yield letter
由於yield存在於__iter__,所以__iter__變成了生成器函數,調用它測返回一個生成器,同時生成器又實現了迭代器協議,所以IterText知足了可迭代的需求。
Python3.3新增長了yield from關鍵字,解決了yield的嵌套循環。例如itertools中的chain函數,它的簡單實現爲下面代碼。
def chain(*iterable): for it in iterable: for i in it: yield i print(list(chain('abc', range(3)))) ## output: ['a', 'b', 'c', 0, 1, 2]
而使用yield from會使代碼變的更加簡潔。
def chain(*iterable): for i in iterable: yield from i print(list(chain('abc', range(3)))) ## output: ['a', 'b', 'c', 0, 1, 2] # 總結