迭代是數(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.finditer 是 re.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)。其中 index 從 start 開始計數(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_len 個 it 產(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')]
PS:itertools.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