0%

collections.Counter源码阅读笔记

这几天用到 collections.Counter 的次数挺多的(比如热门标签、热门愿望、热门城市),看文档的时候就很好奇,这个类初始化的时候是如何实现既可以不传参数,可以传一个可迭代的值,可以传字典,而且还可以传关键字。

文档范例:

1
2
3
4
>>> c = Counter()                           # a new, empty counter
>>> c = Counter('gallahad') # a new counter from an iterable
>>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping
>>> c = Counter(a=4, b=2) # a new counter from keyword args

先来看他的 __init__方法

1
2
3
4
5
6
7
8
9
10
def __init__(*args, **kwds):
if not args:
raise TypeError("descriptor '__init__' of 'Counter' object "
"needs an argument")
self = args[0]
args = args[1:]
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
super(Counter, self).__init__()
self.update(*args, **kwds)

刚开始不明白,明明判断了 args 不存在的话就抛出异常,但是却可以是使用无参来初始化这个类,后来想明白了,类初始化的时候会默认给一个参数,我们通常把这个参数命名为 self,所以才有了下边的语句,将 selfargs[0] 中取出。所以 args[1:] 就是剩下的参数,注意这时候 args 还是一个元组,现在来判断 args 的长度,因为除了关键字参数外,只能使用一个可迭代的值或者一个字典来初始化,所以如果args长度大于1,说明除了关键字参数外,给了超过一个以上的参数,这时候程序抛出异常。

这时候我就想既然是一个参数,为什么不直接给个固定值,比如像这样:

__init(self, iterable_or_mapping, **kwds)__

而是非要作为一个可变参数传入,然后再判断长度,后来想到因为还需要接受无参调用,所以不能用这样写。

这时候又想到能不能这样写呢:

__init(self, iterable_or_mapping=None, **kwds)__

答案是不能,因为你不知道调用者在调用时关键字参数要传什么,万一调用者要传 iterable_or_mapping 作为关键字参数怎么办。

最后想到能不能这样:

__init(self, *args, **kwds)__

觉得好像没什么问题。。。不知道阅读者有没有看出来问题,如果看出这样写有什么问题麻烦告诉我,我把评论关闭了,可以通过邮箱: `[email protected]`

接下来然后调用他的 update 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def update(*args, **kwds):
if not args:
raise TypeError("descriptor 'update' of 'Counter' object "
"needs an argument")
self = args[0]
args = args[1:]
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
iterable = args[0] if args else None
if iterable is not None:
if isinstance(iterable, Mapping):
if self:
self_get = self.get
for elem, count in iterable.iteritems():
self[elem] = self_get(elem, 0) + count
else:
super(Counter, self).update(iterable) # fast path when counter is empty
else:
self_get = self.get
for elem in iterable:
self[elem] = self_get(elem, 0) + 1
if kwds:
self.update(kwds)

刚开始和 __init__ 一样,就不说了,从 iterable = args[0] if args else None 开始说。

因为此时 args 是一个长度小于1的元组,所以 args[0]可能是一个值也可能是 None,如果不是 None的话,就进入 if, 首先判断这个值类型是不是 Mapping,我猜测这里的 Mapping 就是 dict 的原始实现类,只是在注册为了dict(如有误请更正),如果是字典类型的,就把键值对迭代出来。因为这个类继承自 dict,所以继承了 get 方法, Python可以将方法作为参数传递,所以就有了这样的写法 self_get = self.get ,这时候 self_get 实际上还带有这个类的实例(这么说有点别扭。。。),然后 self[elem] = self_get(elem, 0) + countself 中尝试获取 elem 的数量,如果没有就初始化为0然后加上字典的值,这样写而不是直接 self[elem] = count是有原因的,因为我们还可能给关键字参数,这样写的好处是,一会用关键字参数再递归调用一下这个方法,就能把关键字参数的值也更新上去啦。

iterable 不是字典的时候,就会把这个值进行迭代取出每个元素计算出现的次数,具体代码就不解释了。

最后的疑惑:我不明白的是为什么要判断 self 存不存在,在什么情况下初始化一个类会没有 self

下一篇准备写写 Counter 中两个常用方法 most_commonelements。因为现在我还没看。。。