流暢的python讀書筆記-第十四章-可迭代的對象、迭代器和生成器

可迭代的對象、迭代器和生成器

理念

迭代是數據處理的基石。掃描內存中放不下的數據集時,咱們要找到一種惰性獲取數據
項的方式,即按需一次獲取一個數據項。這就是迭代器模式(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)
for 循環就是 用到了index, index在__getitem__被用到了

序列能夠迭代的緣由 iter函數

解釋器須要迭代對象 x 時,會自動調用 iter(x)。dom

內置的 iter 函數有如下做用。ssh

  • (1) 檢查對象是否實現了 iter 方法,若是實現了就調用它,獲取一個迭代器。
  • (2) 若是沒有實現 iter 方法,可是實現了 getitem 方法,Python 會建立一個迭代器,嘗試按順序(從索引 0 開始)獲取元素。
  • (3) 若是嘗試失敗,Python 拋出 TypeError 異常,一般會提示「C object is not iterable」(C對象不可迭代),其中 C 是目標對象所屬的類。

可迭代的對象與迭代器的對比

Python 從可迭代的對象中獲取迭代器函數

標準的迭代器接口有兩個方法。

next
  返回下一個可用的元素,若是沒有元素了,拋出 StopIteration 異常。
iter
  返回 self,以便在應該使用可迭代對象的地方使用迭代器,例如在 for 循環中。spa

clipboard.png

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

如何使用 next(...) 函數使用迭代器

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)))
由於迭代器只需 nextiter 兩個方法,因此除了調用 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 類的構造方法建立一個迭代器並將其返回。

爲何 不寫在一塊兒

  • 把Sentence變成迭代器:壞主意

構建可迭代的對象和迭代器時常常會出現錯誤,緣由是混淆了兩者。

要知道,可迭代的對象有個 iter 方法,每次都實例化一個新的迭代器;
而迭代器要實現 next 方法,返回單個元素,此外還要實現 iter 方法,返回迭代器自己。

所以,迭代器能夠迭代,可是可迭代的對象不是迭代器。

除了 iter 方法以外,你可能還想在 Sentence 類中實現 next 方法,讓
Sentence 實例既是可迭代的對象,也是自身的迭代器。

但是,這種想法很是糟糕。根據有大量 Python 代碼審查經驗的 Alex Martelli 所說,這也是常見的反模式。

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 __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 方法是生成器函數。

生成器函數的工做原理

  • 只要 Python 函數的定義體中有 yield 關鍵字,該函數就是生成器函數。調用生成器函數時,會返回一個生成器對象。也就是說,生成器函數是生成器工廠
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 函數的輸出夾雜在一塊兒。

什麼時候使用生成器表達式

根據個人經驗,選擇使用哪一種句法很容易判斷:若是生成器表達式要分紅多行寫,我傾向
於定義生成器函數,以便提升可讀性。此外,生成器函數有名稱,所以能夠重用。

8 另外一個示例:等差數列生成器

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 的各個值,

以此下降處理浮點數時累積效應致錯的風險。

標準庫裏有大量的生成器輪子 page408

深刻分析iter函數

但是,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)

總結

  • 迭代器 都有 iter這個方法(函數), (每一個都調用__getitem__作兼容) next()到底.
  • for每次迭代都是next()
  • 對付大文件,大內存. 先返回迭代器對象, 在執行迭代器的具體 操做
  • python 用yield 做爲生成器的特徵
  • send能夠發送給 那個狀態的生成器
  • iter別的用法 有兩個參數 第二個是哨符 (遇到就停了)
相關文章
相關標籤/搜索