1. 程式人生 > >叠代器模塊 itertools

叠代器模塊 itertools

排序 這也 有意 需要 合並 文檔 import bre http

無限叠代器

itertools 包自帶了三個可以無限叠代的叠代器。這意味著,當你使用他們時,你要知道你需要的到底是最終會停止的叠代器,還是需要無限地叠代下去。

這些無限叠代器在生成數字或者在長度未知的可叠代對象(iterables)中循環時相當有用。下面我們開始認識這些有趣的可叠代對象!

count(初值=0, 步長=1)

count 叠代器會返回從傳入的起始參數開始的均勻間隔的數值。count 也可以接收指定的步長參數。我們來看一個簡單的例子:

>>> from itertools import count
>>> for i in count(10):
...     if i > 20: 
...         break
...     else:
...         print(i)
... 
10
11
12
13
14
15
16
17
18
19
20

這裏我們先從 itertools 導入 count,然後創建一個 for 循環。循環中加入了條件檢查,當叠代器值大於 20 時跳出循環,否則將打印出叠代器的當前值。你應該註意到了,輸出結果從 10 開始,與我們傳入 count 的起始值是一致的。

另一種控制無限叠代器輸出的方式是使用 itertools 的子模塊 islice。使用方法如下:

>>> from itertools import islice
>>> for i in islice(count(10), 5):
...     print(i)
... 
10
11
12
13
14

這裏,我們先導入了 islice,然後遍歷 count,從 10 開始,輸出 5 個元素後結束。你大概猜到了,islice 的第二個參數控制何時停止叠代。但其含義並不是”達到數字 5 時停止“,而是”當叠代了 5 次之後停止“。

cycle(可叠代對象)

itertools 中的 cycle 叠代器允許你創建一個能在一組值間無限循環的叠代器。下面我們傳入一個 3 個字母的字符串,看看將會發生什麽:

>>> from itertools import cycle
>>> count = 0
>>> for item in cycle(‘XYZ‘):
...     if count > 7:
...         break
...     print(item)
...     count += 1
... 
X
Y
Z
X
Y
Z
X
Y

這裏我們創建了一個 for 循環,使其在三個字母 XYZ 間無限循環。當然,我們並不真地想要永遠循環下去,所以我們添加了一個簡單的計數器來跳出循環。

你也可以用 Python 內建的 next 函數對 itertools 創建的叠代器進行循環:

>>> polys = [‘triangle‘, ‘square‘, ‘pentagon‘, ‘rectangle‘]
 >>> iterator = cycle(polys)
 >>> next(iterator)
 ‘triangle‘
 >>> next(iterator)
 ‘square‘
 >>> next(iterator)
 ‘pentagon‘
 >>> next(iterator)
 ‘rectangle‘
 >>> next(iterator)
 ‘triangle‘
 >>> next(iterator)
 ‘square‘

上面的代碼中,我們創建一個簡單的多邊形組成的列表,然後傳入 cycle。我們用一個變量存儲新建的叠代器,然後將這個變量傳入 next 函數。每一次調用 next 都將返回叠代器中的下一個值。由於叠代器是無限的,我們可以一直調用 next 而永遠不會到盡頭。

repeat(對象[, 次數])

repeat 叠代器會一遍遍地返回傳入的對象直至永遠,除非你設定了 times 參數。這與 cycle 非常相像,除了一點,repeat 不會在多個值之間循環。我們看一個簡單的例子:

>>> from itertools import repeat
>>> repeat(5, 5)
repeat(5, 5)
>>> iterator = repeat(5, 5)
>>> next(iterator)
5
>>> next(iterator)
5
>>> next(iterator)
5
>>> next(iterator)
5
>>> next(iterator)
5
>>> next(iterator)
Traceback (most recent call last):
  Python Shell, prompt 21, line 1
builtins.StopIteration:

這裏,我們先導入 repeat,然後設定其重復五次數字 5。接著,我們連續六次調用 next 函數,觀察它是否工作正常。當運行這段代碼,將引發 StopIteration,因為最後一次調用 next 函數,叠代器中的值已經全部返回了。


可終止的叠代器

多數用 itertools 創建的叠代器並不是無限的。這一節,我們將了解 itertools 中的有限叠代器。為了對於輸出的可讀的結果,我們將使用 Python 內建列表存儲。 如果不使用列表,你將只能打印出 itertools 對象。

accumulate(可叠代對象[, 函數])

accumulate 叠代器將返回累計求和結果,或者傳入兩個參數的話,由傳入的函數累積計算的結果。默認設定為相加,我們趕快試一試吧:

>> from itertools import accumulate
>>> list(accumulate(range(10)))
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

這裏,我們 導入了 accumulate,然後傳入 10 個數字,0-9。叠代器將傳入數字依次累加,所以第一個是 0 ,第二個是 0+1, 第三個是 1+2,如此下去。現在我們導入 operator 模塊,然後添加進去:

>>> import operator
>>> list(accumulate(range(1, 5), operator.mul))
[1, 2, 6, 24]

這裏我們傳入了數字 1-4 到 accumulate 叠代器中。我們還傳入了一個函數:operator.mul,這個函數將接收的參數相乘。所以每一次叠代,叠代器將以乘法代替除法(1×1=1, 1×2=2, 2×3=6, 以此類推)。

accumulate 的文檔中給出了其他一些有趣的例子,例如貸款分期償還,混沌遞推關系等。這絕對值得你花時間去看一看。

chain(*可叠代對象)

chain 叠代器能夠將多個可叠代對象合並成一個更長的可叠代對象。實際上,我參與的一個項目中最近就需要這一功能。我有一個列表,裏面已經包含一些元素,接著想把另外兩個列表添加到最初那個列表中。註意,我們想添加的是兩個列表的元素。最初,我是這樣做的:

>>> my_list = [‘foo‘, ‘bar‘]
>>> numbers = list(range(5))
>>> cmd = [‘ls‘, ‘/some/dir‘]
>>> my_list.extend(cmd, numbers)
>>> my_list
[‘foo‘, ‘bar‘, [‘ls‘, ‘/some/dir‘], [0, 1, 2, 3, 4]]

這並不是我想要的。itertools 模塊提供一個優雅得多的方法用chain 來合並這些列表:

>>> from itertools import chain
>>> my_list = list(chain([‘foo‘, ‘bar‘], cmd, numbers))
>>> my_list
[‘foo‘, ‘bar‘, ‘ls‘, ‘/some/dir‘, 0, 1, 2, 3, 4]

許多聰明的讀者可能想到了,實際上不使用 itertools,也有其他方法能夠實現這一要求。你可以這樣做:

>>> my_list = [‘foo‘, ‘bar‘]
>>> my_list += cmd + numbers
>>> my_list
[‘foo‘, ‘bar‘, ‘ls‘, ‘/some/dir‘, 0, 1, 2, 3, 4]

這些方法當然都是可行的。在我知道 chain 之前,我可能會這樣做,但我個人認為這個例子中, chain 更為優雅,也更容易理解。

chain.from_iterable(可叠代對象)

你也可以用 chain 的一個方法,叫做 from_iterable。這個方法與直接用 chain 有些細微的差別。不同於直接傳入一系列可叠代對象,你必須傳入一個嵌套的列表。我們這就來看一看:

>>> from itertools import chain
>>> numbers = list(range(5))
>>> cmd = [‘ls‘, ‘/some/dir‘]
>>> chain.from_iterable(cmd, numbers)
Traceback (most recent call last):
  Python Shell, prompt 66, line 1
builtins.TypeError: from_iterable() takes exactly one argument (2 given)
>>> list(chain.from_iterable([cmd, numbers]))
[‘ls‘, ‘/some/dir‘, 0, 1, 2, 3, 4]

這裏我們跟之前一樣先導入 chain。我們嘗試傳入兩個列表,但卻出現了 TypeError!為了修正這個錯誤,我們將 cmd 和 numbers 放入一個列表中,將這個嵌套的列表傳入 from_iterable。雖然有點細微差別,但也是很方便使用的。

compress(數據, 選擇器)

子模塊 compress 在用一個可叠代對象過濾另一個可叠代對象時十分有用。這是通過將作為選擇器的可叠代對象取為布爾值列表來實現的。下面是它的實現方式:

>>> from itertools import compress
>>> letters = ‘ABCDEFG‘
>>> bools = [True, False, True, True, False]
>>> list(compress(letters, bools))
[‘A‘, ‘C‘, ‘D‘]

這個例子中,我們有七個字母和五個布爾值構成的列表。我們它們傳入 compress 函數。compress 函數將交替查看兩個可叠代對象。先檢查第一個可叠代對象,然後是第二個,如果第二個的元素為 True,則保留第一個對應元素,如果為 False,則丟棄第一個對應元素。據此,再看看上面的例子,你會發現第一、第三和第四個位置元素為 True,對應的與第一個對象中的 A, C 和 D。

dropwhile(斷言, 可叠代對象)

itertools 還提供了一個小清新的叠代器 dropwhile。只要過濾器判定是 True,dropwhile 叠代器就會排除這些元素。因此,在出現斷言為 False 之前,你不會看到任何輸出結果。這可能導致啟動時間非常長,這點應當註意。

我們看一個來自於 Python 文檔的例子:

>>> from itertools import dropwhile
>>> list(dropwhile(lambda x: x<5, [1,4,6,4,1]))
[6, 4, 1]

這裏,我們先導入 dropwhile,然後傳入一個簡單的 lambda 聲明的函數,如果 x 小於5,lambda 將返回 True ,否則返回 False。dropwhile 將遍歷整個列表,然後將每個元素傳入 lambda。如果 lambda 返回 True 則舍棄該值。一旦遇到了數字 6,lambda 將返回 False,因此,我們將保留數字 6 以及之後余下的元素。

當我了解更多之後發現,用一般的函數比 lambda 函數更為有用。所以我們嘗試創建一個函數,如果參數大於 5,函數返回 True。

>>> from itertools import dropwhile
>>> def greater_than_five(x):
...     return x > 5 
... 
>>> list(dropwhile(greater_than_five, [6, 7, 8, 9, 1, 2, 3, 10]))
[1, 2, 3, 10]

這裏,我們在 Python 解釋器中創建了一個簡單的函數作為斷言或過濾器。如果我們傳入的值為 True,這些值都會被舍棄。一旦我們遇到了一個小於 5 的值,那麽該值,包括其後余下的全部值都將被保留,正如你在例子中看到的。

filterfalse(斷言, 可叠代對象)

itertools 中的 filterfalse 函數與 dropwhile 非常類似。只是,filterfalse 返回斷言為 False 的那些值,而不是舍棄斷言為 True 的值。以我們上一節創建的函數為例來闡釋:

>>> from itertools import filterfalse
>>> def greater_than_five(x):
...     return x > 5 
... 
>>> list(filterfalse(greater_than_five, [6, 7, 8, 9, 1, 2, 3, 10]))
[1, 2, 3]

這裏,我們向 filter 傳入了我們創建的函數和一個整數元素的列表。如果整數值小於 5,這個整數將保留下來,否則將被舍棄。註意到,這裏的結果僅有 1, 2 和 3。與 dropwhile 不同,filterfalse將對全部的值進行條件判斷。

groupby(可叠代對象, =None)

groupby 叠代器會從傳入的可叠代對象返回連續的鍵和組。不借助例子可能很難理解這一點,所以我們還是看例子。將下面的代碼輸入解釋器或者存為一個文件:

from itertools import groupby
 
vehicles = [(‘Ford‘, ‘Taurus‘), (‘Dodge‘, ‘Durango‘),
            (‘Chevrolet‘, ‘Cobalt‘), (‘Ford‘, ‘F150‘),
            (‘Dodge‘, ‘Charger‘), (‘Ford‘, ‘GT‘)]
 
sorted_vehicles = sorted(vehicles)
 
for key, group in groupby(sorted_vehicles, lambda make: make[0]):
    for make, model in group:
        print(‘{model} is made by {make}‘.format(model=model,
                                                 make=make))
    print ("***** END OF GROUP *****")

這裏,我們先導入 groupby,然後創建一個元組構成的列表。接著,我們試圖對數據排序使其輸出時更有意義,這也讓 groubby 能夠正確地對元素分組。下一步,我們遍歷 groupby 返回的含有鍵和組的叠代器。接著我們遍歷組,並且打印出它的內容。如果你運行這段代碼,你應該會看到下面的結果。

Cobalt is made by Chevrolet
***** END OF GROUP *****
 
Charger is made by Dodge
Durango is made by Dodge
***** END OF GROUP *****
 
F150 is made by Ford
GT is made by Ford
Taurus is made by Ford
***** END OF GROUP *****

試試改動一下上面的代碼,將傳入的 sorted_vehicles 替換成 vehicles。你很快就會明白為什麽你要在傳入 groupby 前對數據排序。

islice(可叠代變量, 起始值, 終止值[, 步長])

我們實際上在 count 一節的時候已經提到了 islice,這裏我們將更深入地探討這一函數。islice 是一個返回可叠代對象選定元素的叠代器。這種表述有點模糊。簡而言之,islice 所做的是利用可叠代對象的索引實現切片,然後以叠代器的形式返回所選元素。實際上 islice 有兩種實現方式,一種是 itertools.islice(iterable, stop),還有一種更符合 Python 慣例的形式:islice(iterable, start, stop[, step])。

我們來看看第一種形式,觀察它是如何工作的:

>>> from itertools import islice
>>> iterator = islice(‘123456‘, 4)
>>> next(iterator)
‘1‘
>>> next(iterator)
‘2‘
>>> next(iterator)
‘3‘
>>> next(iterator)
‘4‘
>>> next(iterator)
Traceback (most recent call last):
  Python Shell, prompt 15, line 1
builtins.StopIteration:

在以上的代碼中,我們傳入了一個 6 個字符組成的字符串以及終止參數 4 給 isilce。這表示 islice 返回的叠代器將包含傳入字符串的前 4 個字符。為了驗證,我們連續四次調用 next 函數。Python 能夠智能地識別是否只有 2 個傳入參數,若是,則第二個參數就取為終止參數。

我們再試試傳入三個參數來驗證是否可以同時傳入起始值和終止值。count 工具可以幫助我們解釋這一概念:

>>> from itertools import islice
>>> from itertools import count
>>> for i in islice(count(), 3, 15):
...     print(i)
... 
3
4
5
6
7
8
9
10
11
12
13
14

這裏我們調用了 count,並指定 islice 從 3 開始,到 15 時結束。這跟我們用 slice 效果是一樣的,不同之處在於傳入的是叠代器,返回的是新的叠代器。

starmap(函數, 可叠代對象)

starmap 工具能夠創建一個用傳入的函數和可叠代對象計算的叠代器。如文檔中所言,“map() 和 starmap() 的區別正如 function(a,b) 和 function(*c) 的區別。”

下面來看一個簡單的例子:

>>> from itertools import starmap
>>> def add(a, b):
...     return a+b
... 
>>> for item in starmap(add, [(2,3), (4,5)]):
...     print(item)
... 
5
9

這裏,我們先創建了一個接受兩個參數的求和函數。接下來我們創建了一個 for 循環,並調用 starmap 函數,starmap 函數的第一個傳入參數是我們創建的求和函數,第二個傳入參數是元組構成的列表。starmap 函數接著會將每一個元組傳入求和函數,然後將結果返回到叠代器,正如我們打印出的那樣。

takewhile(斷言, 可叠代對象)

takewhile 模塊與我們之前介紹的 dropwhile 叠代器剛好相反。takewhile 所創建的叠代器,一旦可叠代對象的斷言或過濾器結果為 True 就返回元素。試一試下面的例子,看看它是如何工作的

>>> from itertools import takewhile
>>> list(takewhile(lambda x: x<5, [1,4,6,4,1]))
[1, 4]

這裏,運行 takewhile 時傳入了一個 lambda 函數和一個列表。輸出的僅是可叠代對象的前兩個整數元素。因為 1 和 4 都小於 5,而 6 大於5,一旦 takewhile 遇到 6, 判定條件結果將是 False,可叠代對象余下的元素將會被忽略。

tee(可叠代對象, n=2)

tee 工具能夠從一個可叠代對象創建 n 個叠代器。這意味著你能夠用一個可叠代對象創建多個叠代器。下面這段代碼能大體解釋它是如何工作的:

>>> from itertools import tee
>>> data = ‘ABCDE‘
>>> iter1, iter2 = tee(data)
>>> for item in iter1:
...     print(item)
... 
A
B
C
D
E
>>> for item in iter2:
...     print(item)
... 
A
B
C
D
E

這裏我們創建了一個 5 個字母組成的字符串,然後傳入 tee。由於 tee 的默認參數是 2,我們用兩個變量接收 tee 返回的兩個叠代器。最後,我們分別遍歷了這兩個叠代器,並打印出它們的內容。正如你所見的,它們的內容是相同的。

zip_longest(*可叠代對象, 填充值=None)

zip_longest 叠代器可以用於將兩個可叠代對象配對。如果可叠代對象的長度不同,也可以傳入填充值。我們來看一個來自該函數文檔的一個很初級的例子:

>>> from itertools import zip_longest
>>> for item in zip_longest(‘ABCD‘, ‘xy‘, fillvalue=‘BLANK‘):
...     print (item)
... 
(‘A‘, ‘x‘)
(‘B‘, ‘y‘)
(‘C‘, ‘BLANK‘)
(‘D‘, ‘BLANK‘)

這段代碼裏,我們先導入 zip_longest,然後傳入兩個需要配對的字符串。你會發現,第一個字符串有 4 個字符,而第二個只有 2 個字符。我們還傳入了填充值”BLANK“。當遍歷並打印的時候,會看到返回的是元組。前兩個元組是分別來自兩個字符串第一個和第二個字母的組合。後兩個則插入了填充值。

需要註意的是,如果傳入 zip_longest 的可叠代對象是無限的,那麽你應該用類似 islice 的工具在外部處理函數,以便控制調用的次數。


(本博客轉至伯樂在線 - 張無忌,本博客僅做學習筆記,若原作者有意見,請聯系!)

叠代器模塊 itertools