您的位置:首页 > 编程语言 > Python开发

Python collections模块Counter类深入分析

2017-07-13 15:46 579 查看

Counter类介绍

Counter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value。计数值可以是任意的Interger(包括0和负数)。

counter的初始化函数

Create a new, empty Counter object. And if given, count elements from an input iterable. Or, initialize the count from another mapping of elements to their counts.

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

def __init__(*args, **kwds):
if not args:
raise TypeError("descriptor '__init__' of 'Counter' object needs an argument")
self, *args = args
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,并在最后一步对参数进行了update,源码如下:

def update(*args, **kwds):
if not args:
raise TypeError("descriptor 'update' of 'Counter' object needs an argument")
self, *args = args
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.items():
self[elem] = count + self_get(elem, 0)
else:
super(Counter, self).update(iterable) # fast path when counter is empty
else:
_count_elements(self, iterable)
if kwds:
self.update(kwds)


对于上面的代码

片段1

iterable = args[0] if args else None


这里之所以可以用0去取参数,是因为函数入参时*将多个参数包装成了元组,由于这里只接受一个arg,因此可以直接arg[0]取值

片段2

if isinstance(iterable, Mapping):


dict在collections中也看作是一个Mapping

片段3

self_get = self.get


get方法如下所示,self_get = self.get只是把函数句柄交给了self_get(相当于匿名),get的实现如下

def get(self, key, default=None):
return self[key] if key in self else default


注意这里传入了两个参数,对应的key和default

片段4

for elem, count in iterable.items():


因为是mapping,因此可以使用items方法(具体可见collections mapping,与dict很像)

片段5

_count_elements(self, iterable)


_count_elements方法如下

def _count_elements(mapping, iterable):
'Tally elements from the iterable.'
mapping_get = mapping.get
for elem in iterable:
mapping[elem] = mapping_get(elem, 0) + 1


其中

mapping[elem] = mapping_get(elem, 0) + 1


直接迭代每个元素(不是mapping的情况,如果是list,可以直接在这里进行迭代),并调用get取出对应的元素,关于get,可以看到



这里是一个Mapping元素,在Mapping中没有找到对应的实现,但是在ChainMap中,可以看到下面的实现:

def get(self, key, default=None):
return self[key] if key in self else default


其中,传入两个参数,分别对应着key,和default,其中self指的是_count_elements(mapping, iterable)中的mapping本身,因此,其实就是取出mapping中的elem元素,存在返回,不存在返回0。 注释6代码也就是在这个基础上+1

片段6

self.update(kwds)


如果存在多个参数,则会将后面的键值对包装为dict经嵌套调用后传给arg

结论

这个update函数也完美的说明了,Counter如何处理不同类型的初始化对象,如果传入字典(实际是Mapping)、列表与字符串(非Mapping,但是可迭代)有不同的处理方法,因此只要传入可迭代对象,理论上都是可以进行counter的,但是注意,这个可迭代对象应该是一维的,内部的每个元素应该应该为数字、字符串等简单类型的。

如下面代码:

a = itertools.chain(*[[[1],[2],[3]],[[4],[5],[6]]])


输出结果:

Out[87]: <itertools.chain at 0x1e800cdd0b8>
Counter(a)
Traceback (most recent call last):
File "C:\Users\chauncy\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py", line 2885, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-88-acd8fd1b29c9>", line 1, in <module>
Counter(a)
File "C:\Users\chauncy\Anaconda3\lib\collections\__init__.py", line 530, in __init__
self.update(*args, **kwds)
File "C:\Users\chauncy\Anaconda3\lib\collections\__init__.py", line 617, in update
_count_elements(self, iterable)
TypeError: unhashable type: 'list'
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: