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

posted on 2016-11-03 23:09  老顽童2007  阅读(4523)  评论(0编辑  收藏  举报