Fluent Python 筆記 —— 可迭代對象、迭代器和生成器

迭代是數(shù)據(jù)處理的基石。掃描內存中放不下的數(shù)據(jù)集時,通常需要一種惰性獲取數(shù)據(jù)項的方式,即按需一次獲取一個數(shù)據(jù)項。這就是迭代器模式。

在 Python 中,所有序列類型都支持迭代。在語言內部,迭代器用于支持以下操作:

  • for 循環(huán)
  • 構建和擴展序列類型
  • 逐行遍歷文本文件
  • 列表推導、字典推導和集合推導
  • 元組拆包
  • 調用函數(shù)時,使用 * 拆包實參

可迭代對象

以下代碼實現(xiàn)了一個 Sentence 類,通過索引從文本中提取單詞:

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 f'Sentence({reprlib.repr(self.text)})'

效果如下:

>>> from sentence import Sentence
>>> s = Sentence('"The time has come," the Walrus said,')
>>> s
Sentence('"The time ha... Walrus said,')
>>> for word in s:
...     print(word)
...
The
time
has
come
the
Walrus
said
>>> list(s)
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']

上面創(chuàng)建的 Sentence 實例是可迭代的。因此該實例對象可被 for 循環(huán)調用、可以用于構建列表等。

迭代的機制

Python 解釋器需要迭代對象 x 時,會自動執(zhí)行 iter(x)。其作用如下:

  • 檢查對象是否實現(xiàn)了 __iter__ 方法,如已實現(xiàn)則調用 __iter__,返回一個迭代器對象
  • 若對象沒有實現(xiàn) __iter__ 方法,但實現(xiàn)了 __getitem__ 方法,Python 會創(chuàng)建一個迭代器,嘗試按順序(從索引 0 開始)獲取元素
  • 若上述嘗試失敗,拋出 TypeError 異常(X object is not iterable

所有 Python 序列都實現(xiàn)了 __iter__ 方法,因此都支持迭代操作。

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

可迭代對象指通過 iter 函數(shù)調用可以獲取迭代器的對象。即對象實現(xiàn)了能夠返回迭代器的 __iter__ 方法,該對象就是可迭代的;或者實現(xiàn)了 __getitem__ 方法,且其參數(shù)是從 0 開始的索引,則對象也可以迭代。

一個簡單的 for 循環(huán)背后也是有迭代器的作用的:

>>> s = 'ABC'
>>> for char in s:
...     print(char)
...
A
B
C

使用 while 循環(huán)模擬效果如下:

>>> s = 'ABC'
>>> it = iter(s)
>>> while True:
...     try:
...             print(next(it))
...     except StopIteration:
...             del it
...             break
...
A
B
C
  • 使用可迭代的對象(字符串 s)創(chuàng)建迭代器 it
  • 不斷在迭代器 it 上調用 next 函數(shù),獲取下一個字符
  • 若已獲取到最后一個字符,迭代器拋出 StopIteration 異常
  • 捕獲 StopIteration 異常,釋放 it 對象,退出循環(huán)

Python 語言內部會自動處理 for 循環(huán)和其他迭代上下文(如列表推導等)中的 StopIteration 異常。

迭代器(如前面的 it)實現(xiàn)了無參數(shù)的 __next__ 方法,返回序列中的下一個元素;若沒有元素了,則拋出 StopIteration 異常。Python 中的迭代器還實現(xiàn)了 __iter__ 方法,返回該迭代器本身(即確保迭代器本身也是可迭代對象)

典型的迭代器

關于可迭代對象與迭代器之間的區(qū)別,可以參考如下代碼:

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 f'Sentence({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

根據(jù)迭代器協(xié)議,可迭代對象 Sentence 中的 __iter__ 方法會實例化并返回一個迭代器(SentenceIterator),而 SentenceIterator 作為迭代器實現(xiàn)了 __next____iter__ 方法。

構建可迭代對象時出現(xiàn)錯誤的原因經(jīng)常是混淆了可迭代對象與迭代器。可迭代對象通過內部的 __iter__ 方法返回一個實例化的迭代器對象;而迭代器要實現(xiàn) __next__ 方法返回單個元素,此外還需要實現(xiàn) __iter__ 方法返回迭代器本身。

生成器函數(shù)

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 f'Sentence({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

實現(xiàn)可迭代對象,相較于之前的代碼,符合 Python 習慣的方式是用生成器函數(shù)替換手動實現(xiàn)的迭代器 SentenceIterator 類。

只要 Python 函數(shù)的定義體中有 yield 關鍵字,則該函數(shù)就是生成器函數(shù)。調用生成器函數(shù)會返回一個生成器對象。

>>> def gen_123():
...     yield 1
...     yield 2
...     yield 3
...
>>> gen_123
<function gen_123 at 0x7f63e57f0f80>
>>> gen_123()
<generator object gen_123 at 0x7f63e57dc950>
>>> for i in gen_123():
...     print(i)
...
1
2
3
>>> g = gen_123()
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

把生成器對象傳遞給 next() 函數(shù)時,其行為與迭代器一致。

惰性求值

re.finditerre.findall 函數(shù)的惰性版本,返回的不是結果列表而是一個生成器,按需生成 re.MatchObject 實例。即只在需要時才生成下一個單詞。

import re
import reprlib

RE_WORD = re.compile('\w+')

class Sentence:
    def __init__(self, text):
        self.text = text

    def __repr__(self):
        return f'Sentence({reprlib.repr(self.text)})'

    def __iter__(self):
        for match in RE_WORD.finditer(self.text):
            yield match.group()

finditer 函數(shù)返回一個迭代器,包含 self.text 中匹配 RE_WORD 的單詞,產(chǎn)出 MatchObject 實例。match.group() 方法從 MatchObject 實例中提取匹配正則表達式的具體文本。

生成器函數(shù)已極大地簡化了代碼,但使用生成器表達式能夠把代碼變得更為簡短。

>>> def gen_AB():
...     print('start')
...     yield 'A'
...     print('continue')
...     yield 'B'
...     print('end.')
...
>>> res = (x * 3 for x in gen_AB())
>>> res
<generator object <genexpr> at 0x7f4619324ad0>
>>> for i in res:
...     print('-->', i)
...
start
--> AAA
continue
--> BBB
end.

可以看出,生成器表達式會產(chǎn)出生成器。

import re
import reprlib

RE_WORD = re.compile('\w+')

class Sentence:
    def __init__(self, text):
        self.text = text

    def __repr__(self):
        return f'Sentence({reprlib.repr(self.text)})'

    def __iter__(self):
        return (match.group() for match in RE_WORD.finditer(self.text))

標準庫中的生成器函數(shù)

用于過濾的生成器函數(shù)

模塊 函數(shù) 說明
itertools compress(it, selector_it) 并行處理兩個可迭代對象。若 selector_it 中的元素是真值,產(chǎn)出 it 中對應的元素
itertools dropwhile(predicate, it) 把可迭代對象 it 中的元素傳給 predicate,跳過 predicate(item) 為真值的元素,在 predicate(item) 為假時停止,產(chǎn)出剩余(未跳過)的所有元素(不再繼續(xù)檢查)
內置 filter(predicate, it) it 中的各個元素傳給 predicate,若 predicate(item) 返回真值,產(chǎn)出對應元素
itertools filterfalse(predicate, it) filter 函數(shù)類似,不過 predicate(item) 返回假值時產(chǎn)出對應元素
itertools takewhile(predicate, it) predicate(item) 返回真值時產(chǎn)出對應元素,然后立即停止不再繼續(xù)檢查
itertools islice(it, stop)islice(it, start, stop, step=1) 產(chǎn)出 it 的切片,作用類似于 s[:stop]s[start:stop:step,不過 it 可以是任何可迭代對象,且實現(xiàn)的是惰性操作
>>> def vowel(c):
...     return c.lower() in 'aeiou'
...
>>> list(filter(vowel, 'Aardvark'))
['A', 'a', 'a']
>>> import itertools
>>> list(itertools.filterfalse(vowel, 'Aardvark'))
['r', 'd', 'v', 'r', 'k']
>>> list(itertools.dropwhile(vowel, 'Aardvark'))
['r', 'd', 'v', 'a', 'r', 'k']
>>> list(itertools.compress('Aardvark', (1,0,1,1,0,1)))
['A', 'r', 'd', 'a']
>>> list(itertools.islice('Aardvark', 4))
['A', 'a', 'r', 'd']
>>> list(itertools.islice('Aardvark', 1, 7, 2))
['a', 'd', 'a']

用于映射的生成器函數(shù)

模塊 函數(shù) 說明
itertools accumulate(it, [func]) 產(chǎn)出累積的總和。若提供了 func,則把 it 中的前兩個元素傳給 func,再把計算結果連同下一個元素傳給 func,以此類推,產(chǎn)出結果
內置 enumerate(it, start=0) 產(chǎn)出由兩個元素構成的元組,結構是 (index, item)。其中 indexstart 開始計數(shù),item 則從 it 中獲取
內置 map(func, it1, [it2, ..., itN]) it 中的各個元素傳給 func,產(chǎn)出結果;若傳入 N 個可迭代對象,則 func 必須能接受 N 個參數(shù),且并行處理各個可迭代對象
>>> list(enumerate('albatroz', 1))
[(1, 'a'), (2, 'l'), (3, 'b'), (4, 'a'), (5, 't'), (6, 'r'), (7, 'o'), (8, 'z')]
>>> import operator
>>> list(map(operator.mul, range(11), range(11)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> list(map(operator.mul, range(11), [2, 4, 8]))
[0, 4, 16]
>>> list(map(lambda a, b: (a, b), range(11), [2, 4, 8]))
[(0, 2), (1, 4), (2, 8)]
>>> import itertools
>>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
>>> list(itertools.accumulate(sample))
[5, 9, 11, 19, 26, 32, 35, 35, 44, 45]
>>> list(itertools.accumulate(sample, max))
[5, 5, 5, 8, 8, 8, 8, 8, 9, 9]

合并多個可迭代對象的生成器函數(shù)

模塊 函數(shù) 說明
itertools chain(it1, ..., itN) 先產(chǎn)出 it1 中的所有元素,然后產(chǎn)出 it2 中的所有元素,以此類推,無縫連接
itertools chain.from_iterable(it) 產(chǎn)出 it 生成的各個可迭代對象中的元素,一個接一個無縫連接;it 中的元素應該為可迭代對象(即 it 是嵌套了可迭代對象的可迭代對象)
itertools product(it1, ..., itN, repeat=1) 計算笛卡爾積。從輸入的各個可迭代對象中獲取元素,合并成 N 個元素組成的元組,與嵌套的 for 循環(huán)效果一樣。repeat 指明重復處理多少次輸入的可迭代對象
內置 zip(it1, ..., itN) 并行從輸入的各個可迭代對象中獲取元素,產(chǎn)出由 N 個元素組成的元組。只要其中任何一個可迭代對象到頭了,就直接停止
itertools zip_longest(it1, ..., itN, fillvalue=None) 并行從輸入的各個可迭代對象中獲取元素,產(chǎn)出由 N 個元素組成的元組,等到最長的可迭代對象到頭后才停止。空缺的值用 fillvalue 填充
>>> import itertools
>>> list(itertools.chain('ABC', range(2)))
['A', 'B', 'C', 0, 1]
>>> list(itertools.chain(enumerate('ABC')))
[(0, 'A'), (1, 'B'), (2, 'C')]
>>> list(itertools.chain.from_iterable(enumerate('ABC')))
[0, 'A', 1, 'B', 2, 'C']
>>> list(zip('ABC', range(5)))
[('A', 0), ('B', 1), ('C', 2)]
>>> list(zip('ABC', range(5), [10, 20, 30, 40]))
[('A', 0, 10), ('B', 1, 20), ('C', 2, 30)]
>>> list(itertools.zip_longest('ABC', range(5)))
[('A', 0), ('B', 1), ('C', 2), (None, 3), (None, 4)]
>>> list(itertools.zip_longest('ABC', range(5), fillvalue='?'))
[('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)]
>>> list(itertools.product('ABC', range(2)))
[('A', 0), ('A', 1), ('B', 0), ('B', 1), ('C', 0), ('C', 1)]
>>> suits = 'spades hearts diamonds clubs'.split()
>>> list(itertools.product('AK', suits))
[('A', 'spades'), ('A', 'hearts'), ('A', 'diamonds'), ('A', 'clubs'), ('K', 'spades'), ('K', 'hearts'), ('K', 'diamonds'), ('K', 'clubs')]
>>> list(itertools.product('ABC'))
[('A',), ('B',), ('C',)]
>>> list(itertools.product('ABC', repeat=2))
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), ('B', 'C'), ('C', 'A'), ('C', 'B'), ('C', 'C')]

把輸入的各個元素擴展成多個輸出元素的生成器函數(shù)

模塊 函數(shù) 說明
itertools combinations(it, out_len) 把可迭代對象 it 產(chǎn)出的 out_len 個元素組合在一起產(chǎn)出
itertools combinations_with_replacement(it, out_len) it 產(chǎn)出的 out_len 個元素組合在一起產(chǎn)出,包含相同元素的組合
itertools count(start=0, step=1) start 開始不斷產(chǎn)出數(shù)字,按 step 指定的步幅增加
itertools cycle(it) it 中產(chǎn)出各個元素,存儲各個元素的副本,然后按順序重復不斷地產(chǎn)出各個元素
itertools permutations(it, out_len=None) out_lenit 產(chǎn)出的元素排列在一起,然后產(chǎn)出這些排列;out_len 的默認值等于 len(list(it))
itertools repeat(item, [times]) 重復不斷地產(chǎn)出指定的元素,除非提供 times 指定次數(shù)
>>> import itertools
>>> ct = itertools.count()
>>> next(ct)
0
>>> next(ct), next(ct), next(ct)
(1, 2, 3)
>>> list(itertools.islice(itertools.count(1, .3), 3))
[1, 1.3, 1.6]
>>> cy = itertools.cycle('ABC')
>>> next(cy)
'A'
>>> list(itertools.islice(cy, 7))
['B', 'C', 'A', 'B', 'C', 'A', 'B']
>>> rp = itertools.repeat(7)
>>> next(rp), next(rp)
(7, 7)
>>> list(itertools.repeat(8, 4))
[8, 8, 8, 8]
>>> import itertools
>>> list(itertools.combinations('ABC', 2))
[('A', 'B'), ('A', 'C'), ('B', 'C')]
>>> list(itertools.combinations_with_replacement('ABC', 2))
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]
>>> list(itertools.permutations('ABC', 2))
[('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
>>> list(itertools.product('ABC', repeat=2))
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), ('B', 'C'), ('C', 'A'), ('C', 'B'), ('C', 'C')]

用于重新排列元素的生成器函數(shù)

模塊 函數(shù) 說明
itertools groupby(it, key=None) 產(chǎn)出由兩個元素組成的元素,形式為 (key, group),其中 key 是分組標準,group 是生成器,用于產(chǎn)出分組里的元素
內置 reversed(seq) 從后向前,倒序產(chǎn)出 seq 中的元素;seq 必須是序列,或者實現(xiàn)了 __reversed__ 特殊方法的對象
itertools tee(it, n=2) 產(chǎn)出一個有 n 個生成器組成的元組,每個生成器都可以獨立地產(chǎn)出輸入的可迭代對象中的元素
>>> import itertools
>>> animals = ['duck', 'eagle', 'rat', 'giraffe', 'bear', 'bat', 'dolphin', 'shark', 'lion']
>>> animals.sort(key=len)
>>> animals
['rat', 'bat', 'duck', 'bear', 'lion', 'eagle', 'shark', 'giraffe', 'dolphin']
>>> for length, group in itertools.groupby(animals, len):
...     print(length, '->', list(group))
...
3 -> ['rat', 'bat']
4 -> ['duck', 'bear', 'lion']
5 -> ['eagle', 'shark']
7 -> ['giraffe', 'dolphin']
>>>
>>> g1, g2 = itertools.tee('ABC')
>>> next(g1)
'A'
>>> next(g2)
'A'
>>> next(g2)
'B'
>>> list(g1)
['B', 'C']
>>> list(g2)
['C']
>>> list(zip(*itertools.tee('ABC')))
[('A', 'A'), ('B', 'B'), ('C', 'C')]

PSitertools.groupby 假定輸入的可迭代對象已按照分組標準完成排序

讀取迭代器,返回單個值的函數(shù)

模塊 函數(shù) 說明
內置 all(it) it 中的所有元素都為真值時返回 True,否則返回 False;all([]) 返回 True
內置 any(it) 只要 it 中有元素為真值就返回 True,否則返回 False;any([]) 返回 False
內置 max(it, [key=], [default=]) 返回 it 中值最大的元素;key 是排序函數(shù),與 sorted 中的一樣;若可迭代對象為空,返回 default
內置 min(it, [key=], [default=]) 返回 it 中值最小的元素;key 是排序函數(shù);若可迭代對象為空,返回 default
functools reduce(func, it, [initial]) 把前兩個元素傳給 func,然后把計算結果和第三個元素傳給 func,以此類推,返回最后的結果。若提供了 initial,則將其作為第一個元素傳入
內置 sum(it, start=0) it 中所有元素的總和,若提供可選的 start,會把它也加上
>>> all([1, 2, 3])
True
>>> all([1, 0, 3])
False
>>> all([])
True
>>> any([1, 2, 3])
True
>>> any([1, 0, 3])
True
>>> any([0, 0.0])
False
>>> any([])
False
>>> import functools
>>> functools.reduce(lambda a, b: a * b, range(1, 6))
120
>>> import operator
>>> functools.reduce(operator.mul, range(1, 6))
120

參考資料

Fluent Python

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容