这几天用到 collections.Counter
的次数挺多的(比如热门标签、热门愿望、热门城市),看文档的时候就很好奇,这个类初始化的时候是如何实现既可以不传参数,可以传一个可迭代的值,可以传字典,而且还可以传关键字。
文档范例:
1 | >>> c = Counter() # a new, empty counter |
先来看他的 __init__方法
:
1 | def __init__(*args, **kwds): |
刚开始不明白,明明判断了 args
不存在的话就抛出异常,但是却可以是使用无参来初始化这个类,后来想明白了,类初始化的时候会默认给一个参数,我们通常把这个参数命名为 self
,所以才有了下边的语句,将 self
从 args[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 | def update(*args, **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) + count
从 self
中尝试获取 elem
的数量,如果没有就初始化为0然后加上字典的值,这样写而不是直接 self[elem] = count
是有原因的,因为我们还可能给关键字参数,这样写的好处是,一会用关键字参数再递归调用一下这个方法,就能把关键字参数的值也更新上去啦。
当 iterable
不是字典的时候,就会把这个值进行迭代取出每个元素计算出现的次数,具体代码就不解释了。
最后的疑惑:我不明白的是为什么要判断
self
存不存在,在什么情况下初始化一个类会没有self
?
下一篇准备写写 Counter
中两个常用方法 most_common
和 elements
。因为现在我还没看。。。