Python3中的迭代器和生成器

介紹

本篇將介紹Python3中的迭代器與生成器,描述可迭代與迭代器關係,並實現自定義類的迭代器模式。python

可迭代的(iterable)

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

  1. 檢查對象instance是否實現了__iter__方法,並調用它獲取返回的迭代器(iterator)。
  2. 若是對象沒有實現__iter__方法,可是實現了__getitem__方法,Python會生成一個迭代器。
  3. 若是上述都失敗,則編譯器則拋出TypeError錯誤,‘xxx' Object is not iterable。

自定義類實現__iter__方法

根據第一條,咱們自定義類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 vs iterator(可迭代vs迭代器)

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()函數產生下一個迭代值。實現迭代器須要實現如下兩個方法:

  1. __iter__
    返回self
  2. __next__
    返回下一個可用的元素,若是無可用元素則拋出StopIteration異常

迭代器實現__iter__,所以全部的迭代器都是可迭代的,下圖展現了iterable和iterator的結構。

clipboard.png

迭代器模式

實現一個自定義的迭代器模式須要兩個類,分別爲實現了__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__中能夠返回不一樣的迭代器對象,使功能獨立。

生成器(generator)

經過上述文章說明,迭代器經過next()不斷產出下一個元素直到迭代器耗盡,而Python中的生成器能夠理解爲一個更優雅的迭代器(不須要實現__iter__和__next__方法),實現了迭代器協議,它也能夠經過next()產出元素。
Python中的生成器主要分爲兩種類型:

  • 生成器函數(generator function)返回獲得的生成器:
    包含yield關鍵字的函數稱爲生成器函數
def gen_func():
    yield 1
    yield 2
    yield 3
g = gen_func()
  • 生成器表達式(generator expression)返回獲得的生成器
g = (i for i in (1, 2, 3))

咱們能夠利用生成器進行迭代操做:

for e in g:
    print(e)
    
## 生成器g已被耗盡,若是須要從新迭代須要從新得到新的生成器對象
g = gen_func()
for e in g:
    print(e)

利用生成器代替可迭代中的__iter__迭代器

在迭代器模式章節中,咱們在可迭代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知足了可迭代的需求。

yield from

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]

# 總結
相關文章
相關標籤/搜索