0%

Counter的elements()源码阅读笔记

Counterelements() 方法返回一个迭代器。元素被重复了多少次,在该迭代器中就包含多少个该元素。所有元素按照字母序排序,个数小于1的元素不被包含。

举例:

1
2
3
>>> c = Counter('ABCABC')
>>> sorted(c.elements())
['A', 'A', 'B', 'B', 'C', 'C']

源码如下:

1
2
def elements(self):
return _chain.from_iterable(_starmap(_repeat, self.iteritems()))

好!!!精!!!简!!!

从里往外看这行代码吧:

1
_starmap(_repeat, self.iteritems())

_starmapitertools 模块中的一个实现了 __iter__ 方法的类,构造器接收两个参数:一个函数(function)和一个序列(sequence),作用是创建一个迭代器,生成值function(*item),其中item来自sequence,只有当sequence生成的项适用于这种调用函数的方式时,此函数才有效。

itertools.starmap(function, iterable) 等价于:

1
2
3
def starmap(function, iterable):
for args in iterable:
yield function(*args)

举例:

1
2
3
4
5
6
7
8
9
10
11
from itertools import starmap

values = [(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]
for i in starmap(lambda x,y:(x, y, x*y), values):
print '%d * %d = %d' % i

0 * 5 = 0
1 * 6 = 6
2 * 7 = 14
3 * 8 = 24
4 * 9 = 36

所以 _starmap(_repeat, self.iteritems()) 等价于下边的代码:

1
2
for item in self.iteritems():
yield _repeat(*item)

也就是返回一个迭代器,迭代器的每一项是使用item 解包作为参数来调用 _repeat 的结果。

下边再来看_repeatitertools.repeat(object[, times]),同样也是实现了__iter__方法的类,作用是创建一个迭代器,重复生成object,times(如果已提供)指定重复计数,如果未提供times,将无止尽返回该对象。

等价于:

1
2
3
4
5
6
7
def repeat(object, times=None):
if times is None:
while True:
yield object
else:
for i in xrange(times):
yield object

举例:

1
2
3
4
5
6
7
8
9
10
from itertools import *

for i in repeat('over-and-over', 5):
print i

over-and-over
over-and-over
over-and-over
over-and-over
over-and-over

repeat 很容易理解就不用解释了。

下边我们返回去看_starmap(_repeat, self.iteritems()), 这些完这些,得到的结果是一个迭代器里边每一项依然是个迭代器,每个内层迭代器迭代出的结果是重复生成的项。

可以想象成这样:

1
2
3
>>> _starmap(_repeat, [{'A': 2, 'B': 3, 'C': 4}])

['AA', 'BBB', 'CCCC']

再来看一下chainitertools.chain(*iterables), 将多个迭代器作为参数, 但只返回单个迭代器, 它产生所有参数迭代器的内容, 就好像他们是来自于一个单一的序列。

等价于:

1
2
3
4
def chain(*iterables):
for it in iterables:
for element in it:
yield element

举例:

1
2
3
4
5
6
7
8
for i in chain([1, 2, 3], ['a', 'b', 'c']):
print i
1
2
3
a
b
c

chain 的 类函数 from_iterable 可以理解成接收一个参数,然后将这个参数解包后调用构造器。

以上例子也可以写成:

1
2
3
4
5
6
7
8
for i in chain.from_iterable([[1, 2, 3], ['a', 'b', 'c']]):
print i
1
2
3
a
b
c

所以,用 chain 来合并 _starmap(_repeat, self.iteritems()) 得到的嵌套迭代器后得到的就是我们需要的结果了!

最后再次感叹下Python代码的精简!


更正前几篇中的出现过的一个错误:

字典调用 iteritems 方法得到的并不是一个列表,而是一个迭代器。

之前把 iteritems 一直当成 items 了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> x = {'title':'python web site','url':'www.iplaypython.com'}
>>> x.items()

[('url', 'www.iplaypython.com'), ('title', 'python web site')]
>>> a
[('url', 'www.iplaypython.com'), ('title', 'python web site')]
>>> type(a)
<type 'list'>

>>> f = x.iteritems()
>>> f
<dictionary-itemiterator object at 0xb74d5e3c>
>>> type(f)
<type 'dictionary-itemiterator'> #字典项的迭代器
>>> list(f)
[('url', 'www.iplaypython.com'), ('title', 'python web site')]