Python標准模塊--itertools


1 模塊簡介

Python提供了itertools模塊,可以創建屬於自己的迭代器。itertools提供的工具快速並且節約內存。開發者可以使用這些工具創建屬於自己特定的迭代器,這些特定的迭代器可以用於有效的遍歷。

2 模塊使用

2.1 無限迭代器

itertools中有個三個迭代器是無限迭代的,這就意味着當你在使用它們時,你需要了解你要么從這些迭代器中終止,要么就是無限循環。

count

count(start = 0, step = 1),count迭代器返回一系列值,以傳入的start參數開始,Count也可以接受step參數。

from itertools import count

for i in count(10):
    if i > 20:
        break
    else:
        print i,

通過條件判斷,如果超出20,就從for循環中break出來,否則,就打印迭代器中的值,控制台輸出,

10 11 12 13 14 15 16 17 18 19 20

另一種限制無限迭代器的輸出是通過itertools中的islice方法,如下所示,

from itertools import count,islice

for i in islice(count(10),10):
    print i,

count從10開始,在10個元素之后結束。islice的第二個變量是指定何時停止迭代,但是,它並不是"當達到10時停止",而是"當達到10次迭代時停止",控制台輸出,

10 11 12 13 14 15 16 17 18 19

cycle

itertools中的cycle迭代器允許開發者在一個序列上創建一個無限循環的迭代器。使用一個for循環在三個字母"XYZ"中構成無限循環。當然,我們並不期待永遠循環下去,所以設置了一個簡單的計數器,用於終止循環。

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

也可以使用Python內置的next函數在itertools所創建的迭代器上迭代。

>>> from itertools import cycle
>>> polys = ['a','b','c','d']
>>> iterator = cycle(polys)
>>> next(iterator)
'a'
>>> next(iterator)
'b'
>>> next(iterator)
'c'
>>> next(iterator)
'd'
>>> next(iterator)
'a'
>>> next(iterator)
'b'

上述代碼,我創建了一個簡單的列表,並且將它傳遞給cycle。我將新的迭代器保存到一個變量中,並將這個變量傳遞給next函數,每次我調用next函數,它都會返回迭代器中的下一個值,由於迭代器是無限的,因此我們一直調用next函數,也不會超出元素的范圍。

repeat

repeat迭代器返回一個又一個對象,除非你設定了次數。repeat類似於cycle,但是它不會在一個集合中重復循環。引入repeat,並且指定重復數字5次,然后我們在新的迭代器上調用next函數6次,當運行這段代碼時,就會發現StopIteration錯誤被拋出,因為我們運行超出了我們的迭代器。

>>> from itertools import repeat
>>> repeat(4,5)
repeat(4, 5)
>>> iterator = repeat(4,5)
>>> next(iterator)
4
>>> next(iterator)
4
>>> next(iterator)
4
>>> next(iterator)
4
>>> next(iterator)
4
>>> next(iterator)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    next(iterator)
StopIteration

2.2 有限迭代器

大部分你通過itertools所創建的迭代器都不是無限的。在這部分,我們將會學習itertools中有限的迭代器,為了讓輸出可讀性強,我們使用Python內置的list類值,如果你不使用list,你就會僅僅打印出迭代器對象。

accumulate

accumulate迭代器(Python3 中提供)返回累加之和或者兩個函數(開發者可以傳遞給accumulate)的累計結果,accumulate的默認操作是相加,如下,首先我們引入accumulate方法,然后傳遞給它0-9這個序列,它就會將它們依次相加,例如第一個是0,第二個是0+1,第三個是1+2,等等;

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

下面我們引入operator模塊,我們首先將數字1-4傳遞給accumulate迭代器,另外又將operator.mul傳遞給它,它接受這個函數用於相乘。所以每次迭代,它相乘而非是相加(1 * 1 = 1,1 * 2 = 2,2 * 3 = 6,等等)。

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

accumulate文檔給出了一些其他有趣的例子,例如分期付款、債務或者混沌的遞歸關系等等,開發者應該首先明確這些例子應當是你值得花時間。

chain

chain迭代器會將一系列可迭代對象平鋪為一個長的可迭代對象。首先我們有一個具有一些元素的列表,另外還有兩個其他的列表,我們想將這兩個列表添加到原始的列表,但是我們僅僅是想將每一個列表中的元素添加到原始列表中,而不是創建列表中的列表。原始操作如下,

>>> my_list = ['foo','bar']
>>> numbers = list(range(5))
>>> cmd = ['ls','/home']
>>> my_list.extend((numbers,cmd))
>>> my_list
['foo', 'bar', [0, 1, 2, 3, 4], ['ls', '/home']]
>>> from itertools import chain
>>> my_list = list(chain(['foo','bar'],cmd,numbers))
>>> my_list
['foo', 'bar', 'ls', '/home', 0, 1, 2, 3, 4]

還有另一種機智的做法來完成上述工作,而不使用itertools,

>>> my_list = ['foo','bar']
>>> my_list += cmd + numbers
>>> my_list
['foo', 'bar', 'ls', '/home', 0, 1, 2, 3, 4]

上述兩種方法都是有效的,在我知道chain方法之前,我極有可能會使用這種方式,但是我認為這個場景中,chain是一種更優雅並且更容易理解的方法。

chain.from_iterable

我們也可以使用chain里的方法from_iterable,這個方法與直接使用chain有些不同。你需要傳遞一個嵌套的列表,而非直接傳遞一系列可迭代對象。

>>> from itertools import chain
>>> numbers = list(range(5))
>>> cmd = ['ls','/home']
>>> chain.from_iterable(cmd,numbers)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: from_iterable() takes exactly one argument (2 given)
>>> list(chain.from_iterable([cmd,numbers]))
['ls', '/home', 0, 1, 2, 3, 4]

正如之前所做的,這里我們首先引入chain,我們嘗試着將我們的兩個列表傳遞給chain.from_iterable,但是我們卻得到TypeError。為了解決這個問題,我們稍微修改了調用方式,我們將cmd和numbers放入一個列表中,然后再將這個嵌套的列表傳入from_iterable,這是一個細微的區別,但是依然容易使用。

compress

compress子模塊可通過第二個迭代對象對第一個迭代對象進行過濾,主要是通過將第二個迭代對象設置為一個布爾型的列表(或者1和0也可以),例如,

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

在這個例子中,我們右一個七個字母的字符串和一個5個布爾變量的列表。我們將它們傳遞給compress函數,compress函數將會遍歷可迭代對象並且檢查第一個可迭代對象是否滿足第二個可迭代對象,如果第二個可迭代對象的元素是True,那么第一個可迭代對象中相應元素將會保留,如果是False,第一個可迭代對象中相應元素將會被丟棄。注意到上面的例子,我們在第一個、第三個和第五個位置是True,因此我們得到A,C和D。

dropwhile

dropwhile是itertools中一個小巧的迭代器。只要過濾器的標准是True,這個迭代器就會一直丟棄元素,所以你看到這個迭代器沒有任何輸出直到判斷變為False,所以我們要意識到,這將會導致啟動時間變長。

>>> 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

filterfalse函數(Python3支持,Python2是ifilterfalse)類似於dropwhile,不同於丟棄匹配為True的元素,filterfalse僅僅返回那些評估為False的值,讓我們以上述的例子為例,

>>> 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]

在這里,我們將我們定義的函數和一個整數列表傳入filterfalse,如果整數小於5,那么它就被保留,否則就被丟棄,你將會注意到結果僅僅是1,2,3,與dropwhile不同,filterfalse將會檢查每個元素是否滿足判定。

groupby

groupby迭代器將會從迭代對象中返回連續的keys和groups。

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 *****\n")

這里我們首先引入groupby,並且創建了一個元組類型的列表。然后對數據進行排序使得輸出數據更加密集,並且使得groupby正確地將元素聚集在一起。然后我們遍歷groupby返回的迭代器,這個迭代器返回相應的key和group,然后我們遍歷group,並且將其中的元素打印出來,控制台輸出如下,

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 *****

你可以嘗試直接將vehicles傳遞給groupby,而非sorted_vehicles,你很快就會知道為什么在groupby之前要對數據進行排序了。

from itertools import groupby

vehicles = [('Ford','Taurus'),('Dodge','Durango'),('Chevrolet','Cobalt'),('Ford','F150'),('Dodge','Charger'),('Ford','GT')]

sorted_vehicles = 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 *****\n")

控制台輸出,

Taurus is made by Ford
***** End of Group *****

Durango is made by Dodge
***** End of Group *****

Cobalt is made by Chevrolet
***** End of Group *****

F150 is made by Ford
***** End of Group *****

Charger is made by Dodge
***** End of Group *****

GT is made by Ford
***** End of Group *****

islice

我們已經在count這個部分提到過islice,但是在這里,我們將會更深入的研究它。islice一個從可迭代對象中返回所選擇元素的迭代器。這是一種不透明的特性。islice在你的可迭代對象上通過索引做個切斷,將所選擇的元素作為迭代器返回。islice有兩種實現,一種是itertools.islice(iterable,stop),另一個版本是islice(iterable,start,stop,[,step]),更接近Python的切斷。

>>> 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):
  File "<input>", line 1, in <module>
StopIteration

上述代碼中,我們將6個字符的字符串和數字4(表示停止變量)傳入islice,這意味着islice返回的迭代器擁有字符串的前4個元素。我們通過在可迭代對象上調用next4次,來驗證這個結論。如果只有兩個變量傳入islice,Python會自動的將第二個變量作為停止參數。

讓我們來嘗試傳入三個參數,來表示我們可以傳入開始、停止參數。

>>> from itertools import islice,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結束。它就是在一個迭代器做了切斷,並返回一個新的迭代器。

starmap

starmap會創建一個新的迭代器,這個迭代器使用傳入函數和可迭代對象進行計算,正如文檔提到的,map()和starmap()的區別就是傳入的函數分別是function(*c)和function(a,b)。

>>> 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]

在這里,我們將lambda函數和一個列表傳入takewhile,輸出只有可迭代對象中的前兩個元素。原始是1和4都是小於5,而6是大於5的,一旦takewhile遇到6,條件就會變為False,它也就會忽略可迭代對象中剩余的元素。

tee

tee可以從一個可迭代對象中創建出n個迭代器,這意味着你可以從一個可迭代對象中創建出多個迭代器。

>>> from itertools import tee
>>> data = "ABCDE"
>>> iter1,iter2 = tee(data)
>>> iter1
<itertools.tee object at 0x7f183a02e7a0>
>>> list(iter1)
['A', 'B', 'C', 'D', 'E']
>>> list(iter2)
['A', 'B', 'C', 'D', 'E']

在這里,我們創建一個5字符的字符串,然后將它傳遞給tee。由於tee默認是2,我們使用多變量賦值,將tee的返回結果賦值給兩個迭代器。最終我們將每個迭代器打印出來,你可以發現它們的內容是相同的。

zip_longest

zip_longest(Python3支持)可以用於將兩個可迭代對象打包在一起,如果可迭代對象的長度不同,你可以傳入fillvalue,

>>> 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個字符,我們設置了fillvalue = 'BLANK',當我們遍歷元素並且將其打印出來時,你可以觀察到我們得到的是元素。

前兩個元組是第一個字符串和第二個字符中相應的字母,最后兩個元素是fillvalue。

如果傳入到zip_longest中是無限的迭代對象,這時候需要我們通過islice等限制調用次數。

2.3 組合產生器

itertools包含了4個可用於創建數據排列組合的迭代器。

combinations

如果你需要創建組合,Python提供了itertools.combinations。combinations允許你從一個可迭代對象中創建一個迭代器,迭代中的元素長度都相同。

>>> from itertools import combinations
>>> list(combinations('WXYZ',2))
[('W', 'X'), ('W', 'Y'), ('W', 'Z'), ('X', 'Y'), ('X', 'Z'), ('Y', 'Z')]

當你運行這段代碼時,你將會注意到組合返回的是元組,為了讓輸出更有可讀性,在迭代器上循環,將元素聯合成一個單獨的字符串。

>>> from itertools import combinations
>>> for item in combinations('WXYZ',2):
...     print(''.join(item))
...     
...
WX
WY
WZ
XY
XZ
YZ

現在相對容易的查看組合結果。如果迭代對象已經排序,組合函數在組合時就會按照排序的順序進行組合。如果輸入的元素都是不重復的,那么組合不會產生重復的組合結果。

combinations_with_replacement

combinations_with_replacement類似於combinations,唯一的區別就是它會創建重復的組合。

>>> from itertools import combinations_with_replacement
>>> for item in combinations_with_replacement('WXYZ',2):
...     print(''.join(item))
...     
...
WW
WX
WY
WZ
XX
XY
XZ
YY
YZ
ZZ

正如你所看到的,結果中有四個新的元素:WW,XX,YY,ZZ。

product

product迭代器從一系列輸入中創建笛卡爾積。

>>> from itertools import product
>>> arrays = [(-1,1),(-3,3),(-5,5)]
>>> cp = list(product(*arrays))
>>> cp
[(-1, -3, -5), (-1, -3, 5), (-1, 3, -5), (-1, 3, 5), (1, -3, -5), (1, -3, 5), (1
, 3, -5), (1, 3, 5)]

在這里,我們首先引入product,將一個元組列表賦值給變量arrays,然后我們調用product。你將會注意到我們調用product時,使用的是 arrays,這個就會讓這個列表以序列的方式應用在product函數中,這意味着你傳入了3個變量而非1個。如果你願意,你可以將前面的號去掉,看看會發生什么。

>>> cp = list(product(arrays))
>>> cp
[((-1, 1),), ((-3, 3),), ((-5, 5),)]

permutations

permutations迭代器將會從可迭代對象中返回連續的、長度為r的元素排列,和combinations一樣,permutations也是從排序順序進行排列。

>>> from itertools import permutations
>>> for item in permutations('WXYZ',2):
...     print(''.join(item))
...     
...
WX
WY
WZ
XW
XY
XZ
YW
YX
YZ
ZW
ZX
ZY

你將會注意到輸出結果要比combinations的輸出結果要長。當你使用permutations時,它將會排列出字符串的所有組合,如果輸入元素不重復,它不會有重復值。

2.4 總結

itertools是一個多功能的工具集合,你可以使用它們來創建屬於你自己的迭代器或者排列組合。你可以在Python官方文檔學習更多豐富的示例,會讓你更加了解如何使用這個有價值的庫。

3 Reference

Python 201

Python:itertools模塊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM