1. 程式人生 > >Python3中的迭代器和生成器

Python3中的迭代器和生成器

介紹

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

可迭代的(iterable)

Python標準庫中存在著一些可迭代物件,例如:list, tuple, dict, set, str等。
可以對這些迭代物件,進行for-in等迭代操作,例如:

for s in "helloworld":
    print(s)

編譯器若想迭代一個物件a,則會自動呼叫iter(a)獲取該物件的迭代器(iterator),如果iter(a)丟擲異常,則物件a不可迭代。

判斷物件是否可迭代

原生函式iter(instance) 可以判斷某個物件是否可迭代,它的工作流程大概分為以下3個步驟:

  1. 檢查物件instance是否實現了__iter__方法,並呼叫它獲取返回的迭代器(iterator)。
  2. 如果物件沒有實現__iter__方法,但是實現了__getitem__方法,Python會生成一個迭代器。
  3. 如果上述都失敗,則編譯器則丟擲TypeError錯誤,‘xxx’ Object is not iterable。

自定義類實現__iter__方法

根據第一條,我們自定義類Iter1實現__iter__方法使該類的物件可迭代。

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方式包裝程式碼塊。

iterable vs iterator(可迭代vs迭代器)

iterable定義

任何可以由原生函式iter獲取到迭代器的物件
任何實現了__iter__方法並返回迭代器的物件
所有的序列(實現了__getitem__)

Python通過獲取到可迭代物件的迭代器(iterator)實現迭代,例如for-in的實現其實是在內部獲取到了迭代器進行操作。for-in機制可以理解為下述程式碼:

s = 'hello'
it = iter(s)
while (True):
    try:
        print(next(it))
    except StopIteration:
        del it
        break

StopIteration異常將在迭代器耗盡後被丟擲,for-in、生成式(comprehension)、元組解壓(tuple unpacking)等迭代操作都會處理並這個異常。

迭代器是個迭代值生產工廠,它儲存迭代狀態,並通過next()函式產生下一個迭代值。實現迭代器需要實現以下兩個方法:

  1. __iter__
    返回self
  2. __next__
    返回下一個可用的元素,如果無可用元素則丟擲StopIteration異常

迭代器實現__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__中可以返回不同的迭代器物件,使功能獨立。

生成器(generator)

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

  1. 生成器函式(generator function)返回得到的生成器
    包含yield關鍵字的函式稱為生成器函式
def gen_func():
    yield 1
    yield 2
    yield 3
g = gen_func()
  1. 生成器表示式(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]

總結

本篇介紹了Python中的可迭代(iterable)、迭代器(iterator)以及它們的關係,並講述了迭代器模式的實現,同時通過Python中的生成器完善了迭代器模式。