Python collections中的Counter作用以及源码分析
2016-09-24 21:00
453 查看
collections — High-performance container datatypes
在2.4版本中新加入,源代码Lib/collections.py和Lib/_abcoll.py。该模块实现了专用的容器数据类型来替代python的通用内置容器:dict(字典),list(列表), set(集合)和tuple(元组)。容器 | 描述 | 引入版本 |
---|---|---|
namedtuple() | 使用工厂方法创建带有命名的字段的元组的子类 | 2.6 |
deque | 类似列表的容器,能够快速响应在任何一端进行pop | 2.4 |
Counter | 字典子类,为可以进行哈希的对象计数 | 2.7 |
OrderedDict | 字典子类,记录了字典的添加次序 | 2.7 |
defaultdict | 字典子类,调用一个工厂方法来提供缺失的值 | 2.5 |
Counter
counter工具用于支持便捷和快速地计数,例如from collections import Counter cnt = Counter() for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']: cnt[word] += 1 print cnt
输出为
Counter({'blue': 3, 'red': 2, 'green': 1})
Counter类介绍
class collections.Counter([iterable-or-mapping])
一个Counter是dict子类,用于计数可哈希的对象。这是一个无序的容器,元素被作为字典的key存储,它们的计数作为字典的value存储。Counts允许是任何证书,包括0和负数。Counter和其它语言中的bags或者multisets类似。Counter中的元素来源如下:
>>> c = Counter() # a new, empty counter >>> c = Counter('gallahad') # a new counter from an iterable >>> c = Counter({'red': 4, 'blue': 2}) # a new counter from a mapping >>> c = Counter(cats=4, dogs=8) # a new counter from keyword args
Counter对象有一个字典接口除了它们在缺失的items时候返回0而不是产生一个KeyError。设置计数为0并不会从一个counter中删除该元素,使用del来彻底删除。
>>> c = Counter(['eggs', 'ham']) >>> c['bacon'] # count of a missing element is zero 0 >>> c['sausage'] = 0 # counter entry with a zero count >>> del c['sausage'] # del actually removes the entry
在2.7版本的python中,Counter额外支持字典中没有的三个功能
功能 | 描述 |
---|---|
elements() | 返回一个迭代器,其中的元素重复次数和它的count一样多,元素返回次序任意,如果count小于1,将会被忽略 |
most_common( ) | 从多到少返回一个有前n多的元素的列表,如果n被忽略或者为none,返回所有元素,相同数量的元素次序任意 |
subtract([iterable-or-mapping]) | 从当前Counter中扣除元素 |
>>> c = Counter(a=4, b=2, c=0, d=-2) >>> list(c.elements()) ['a', 'a', 'a', 'a', 'b', 'b'] >>> Counter('abracadabra').most_common(3) [('a', 5), ('r', 2), ('b', 2)] >>> c = Counter(a=4, b=2, c=0, d=-2) >>> d = Counter(a=1, b=2, c=3, d=4) >>> c.subtract(d) >>> c Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})
一些字典的常规方法对Counter对象有效,除了两个函数对于Counter的效用有些异常
异常函数 | 描述 |
---|---|
fromkeys(iterable) | 没有为Counter对象实现该函数 |
update([iterable-or-mapping]) | 增加count而不是用新的count取代旧的count |
sum(c.values()) # total of all counts c.clear() # reset all counts list(c) # list unique elements set(c) # convert to a set dict(c) # convert to a regular dictionary c.items() # convert to a list of (elem, cnt) pairs Counter(dict(list_of_pairs)) # convert from a list of (elem, cnt) pairs c.most_common()[:-n-1:-1] # n least common elements c += Counter() # remove zero and negative counts
此外还为Counter提供了一些运算符来进行Counter对象的组合。加法和减法是对对应元素count的加减,与和或返回相应元素的最小/最大的count。输出的结果中会排除掉count小等于0的元素
>>> c = Counter(a=3, b=1) >>> d = Counter(a=1, b=2) >>> c + d # add two counters together: c[x] + d[x] Counter({'a': 4, 'b': 3}) >>> c - d # subtract (keeping only positive counts) Counter({'a': 2}) >>> c & d # intersection: min(c[x], d[x]) Counter({'a': 1, 'b': 1}) >>> c | d # union: max(c[x], d[x]) Counter({'a': 3, 'b': 2}) >>> c = Counter(a=3, b=1, e=1) >>> d = Counter(a=1, b=2, f=-2) >>> c + d # add two counters together: c[x] + d[x] Counter({'a': 4, 'b': 3, 'e': 1}) >>> c - d # subtract (keeping only positive counts) Counter({'a': 2, 'f': 3, 'e': 1}) >>> c & d # intersection: min(c[x], d[x]) Counter({'a': 1, 'b': 1}) >>> c | d # union: max(c[x], d[x]) Counter({'a': 3, 'b': 2, 'e': 1})
注意,Counters原来设计是使用正整数来代表计数,然而,需要小心避免不必要地排除了需要其他类型或者负数值的用例。为了帮助这些用例,以下介绍了最小范围和类型的限制。
Counter类自身是一个字典子类,没有限制其key和value,value原为代表计数的数值,但是你能够存储任何东西在value字段
most_common方法需要value能够进行排次序。
对于in-place operations 例如c[key] += 1,value类型只需要支持加减,所以分数,浮点数和小数将能够正常工作,负数也能够被支持。这对于update和subtract方法也适用。
多重集的方法仅仅被设计于适用大于0的数,输入可以有0和负数,但是输出中只会产生大于0的数值的输出。对于数值类型没有限制,但是数值类型需要支持加法,减法和比较。
elements()方法需要整数的计数,将会忽略0和负数的计数。
源码分析
__init__
先来查看一下Counter中的__init__class Counter(dict): def __init__(*args, **kwds): '''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 ''' 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)
可以看到,在__init__中主要是先调用父类(dict)的初始化,然后使用update函数来更新参数。
update
查看一下update函数def update(*args, **kwds): '''Like dict.update() but add counts instead of replacing them. Source can be an iterable, a dictionary, or another Counter instance. >>> c = Counter('which') >>> c.update('witch') # add elements from another iterable >>> d = Counter('watch') >>> c.update(d) # add elements from another counter >>> c['h'] # four 'h' in which, witch, and watch 4 ''' # The regular dict.update() operation makes no sense here because the # replace behavior results in the some of original untouched counts # being mixed-in with all of the other counts for a mismash that # doesn't have a straight-forward interpretation in most counting # contexts. Instead, we implement straight-addition. Both the inputs # and outputs are allowed to contain zero and negative counts. 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)
可以看到,主要就是对输入中的每个元素,使用get方法获取其计数,如果没有就为0,然后增加计数器的值。如果counter为空,就直接调用dict中的update。
subtract
源码和update相似,但是是进行计数的减法运算。__missing__
再分析其它的源代码,看到def __missing__(self, key): 'The count of elements not in the Counter is zero.' # Needed so that self[missing_item] does not raise KeyError return 0
看到这个方法是处理元素缺失时候返回0,那么是在什么地方调用这个函数的呢?在Counter的源代码里面没有用到这个__missing__方法,那是不是在父类dict中定义了这个方法,然后在子类中覆盖了这个方法呢?但是在__builtin__中间dict类的定义中并没有找到该方法,最后只有找实现了dict的源代码看看,发现有一段是这样写的:
ep = (mp->ma_lookup)(mp, key, hash); if (ep == NULL) return NULL; v = ep->me_value; if (v == NULL) { if (!PyDict_CheckExact(mp)) { /* Look up __missing__ method if we're a subclass. */ PyObject *missing, *res; static PyObject *missing_str = NULL; missing = _PyObject_LookupSpecial((PyObject *)mp, "__missing__", &missing_str); if (missing != NULL) { res = PyObject_CallFunctionObjArgs(missing, key, NULL); Py_DECREF(missing); return res; } else if (PyErr_Occurred()) return NULL; } set_key_error(key); return NULL; } else Py_INCREF(v);
看到,当当前为dict的子类的时候,就回去查找__missing__方法,如果有该方法的话就返回该方法产生的值,如果没有的话,或者不是dict的子类而是dict自身,就会产生一个KeyError。
关于__missing__总结一下就是dict本身没有这个方法,但是如果当前类为dict的子类的话,会在缺失的情况下查看有没有实现__missing__方法,如果有的话,就返回__miss__方法的值。所以Counter作为dict的子类实现了__missing__方法,在缺失的时候返回0。这也就是为什么在Counter类中,如果找不到key,会返回0而不是产生一个KeyError。
most_common
再分析其它的源代码,看到most_common方法def most_common(self, n=None): '''List the n most common elements and their counts from the most common to the least. If n is None, then list all element counts. >>> Counter('abcdeabcdabcaba').most_common(3) [('a', 5), ('b', 4), ('c', 3)] ''' # Emulate Bag.sortedByCount from Smalltalk if n is None: return sorted(self.iteritems(), key=_itemgetter(1), reverse=True) return _heapq.nlargest(n, self.iteritems(), key=_itemgetter(1))
可以看到,如果n的值没有被指定或者为None,就是返回按照计数从大到小排序的所有的元素,如果n被指定且不是None,那就调用了heapq.py中的nlargest的方法,看看这个方法的源代码:
def nlargest(n, iterable): """Find the n largest elements in a dataset. Equivalent to: sorted(iterable, reverse=True)[:n] """ if n < 0: return [] it = iter(iterable) result = list(islice(it, n)) if not result: return result heapify(result) _heappushpop = heappushpop for elem in it: _heappushpop(result, elem) result.sort(reverse=True) return result _nlargest = nlargest def nlargest(n, iterable, key=None): """Find the n largest elements in a dataset. Equivalent to: sorted(iterable, key=key, reverse=True)[:n] """ # Short-cut for n==1 is to use max() when len(iterable)>0 if n == 1: it = iter(iterable) head = list(islice(it, 1)) if not head: return [] if key is None: return [max(chain(head, it))] return [max(chain(head, it), key=key)] # When n>=size, it's faster to use sorted() try: size = len(iterable) except (TypeError, AttributeError): pass else: if n >= size: return sorted(iterable, key=key, reverse=True)[:n] # When key is none, use simpler decoration if key is None: it = izip(iterable, count(0,-1)) # decorate result = _nlargest(n, it) return map(itemgetter(0), result) # undecorate # General case, slowest method in1, in2 = tee(iterable) it = izip(imap(key, in1), count(0,-1), in2) # decorate result = _nlargest(n, it) return map(itemgetter(2), result) # undecorate
上面需要注意的是,定义了两个的nlargest方法,而Python本身并没有函数重载这样的机制,所以后面的nlargest会覆盖前面的nlargest(不知道说法是不是准确,但是测试过效果是这样),由于两个方法之间使用了_nlargest=nlargest,所以_nlargest指向的是前一个的nlargest方法。也就是,我们使用heapq.py中的nlargest时候,使用的是后面一个的方法,如果使用_largest则使用的是前面的一个的方法。后面一个nlargest方法本质上就是对输入进行分析和包装,然后再调用前面一个nlargest的方法。而前面一个nlargest本质上使用的就是最大堆,然后返回前n个结果。
elements
源代码如下:def elements(self): '''Iterator over elements repeating each as many times as its count. >>> c = Counter('ABCABC') >>> sorted(c.elements()) ['A', 'A', 'B', 'B', 'C', 'C'] # Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1 >>> prime_factors = Counter({2: 2, 3: 3, 17: 1}) >>> product = 1 >>> for factor in prime_factors.elements(): # loop over factors ... product *= factor # and multiply them >>> product 1836 Note, if an element's count has been set to zero or is a negative number, elements() will ignore it. ''' # Emulate Bag.do from Smalltalk and Multiset.begin from C++. return _chain.from_iterable(_starmap(_repeat, self.iteritems()))
其中用到了itertools中的一些方法
strmap,将给定sequence中的元组作为参数传入function,例如starmap(pow, [(2,5), (3,2), (10,3)]) –> 32 9 1000
repeat(object [,times]) 则是根据输入将object重复times次,返回迭代器。没有指定则不断产生object。
chain则是将所有输入中的可迭代对象拼接起来。
fromkeys
和dict不同,必须自己实现这个方法,如果没有实现就是用,会抛出NotImplementedError@classmethod def fromkeys(cls, iterable, v=None): # There is no equivalent method for counters because setting v=1 # means that no element can have a count greater than one. raise NotImplementedError( 'Counter.fromkeys() is undefined. Use Counter(iterable) instead.')
copy
返回浅拷贝def copy(self): 'Return a shallow copy.' return self.__class__(self)
__reduce__
当pickler遇到一个其无法识别类型的对象(例如一个扩展类型)的时候,其在两个方面查找pickle的途径,一个是对象实现了__reduce__()方法,如果提供了__reduce__()方法,在pickle的时候会调用__reduce__()方法,该方法会返回一个字符串或者一个元组。如果返回一个字符串,这是一个代表全局变量的名字,其内容会被正常pickle。__reduce__()方法返回的字符串应该是对象和其模块相关的local name。pickle模块搜索模块的命名空间来决定对象的模块。
如果返回一个元组,其必须包含二到五个元素,分别如下:
一个可调用的对象能够用于创建对象的初始化版本,元组的下一个元素将会为这次调用提供参数,后续的元素提供额外的状态信息能够在随后在pickled之后的数据上重建数据。
一个可调用对象的参数元组
(可选)对象的状态,能够被传递给对象的__setstate__()方法
(可选)一个产生被pickle的列表元素的迭代器
(可选)一个产生被pickle的字典元素的迭代器
def __reduce__(self): return self.__class__, (dict(self),)
源代码中返回了两个参数,一个是类的名字,一个是参数元组,里面将自身转换为dict。
__delitem__
__delitem__为del obj[key]所需的方法(官网说明),源码如下:def __delitem__(self, elem): 'Like dict.__delitem__() but does not raise KeyError for missing values.' if elem in self: super(Counter, self).__delitem__(elem)
本质上就是一个不抛出KeyError的dict类的__delitem()__
__repr__
__repr__是“Called by the repr() built-in function and by string conversions (reverse quotes) to compute the “official” string representation of an object.”源码中对__repr__实现如下:
def __repr__(self): if not self: return '%s()' % self.__class__.__name__ items = ', '.join(map('%r: %r'.__mod__, self.most_common())) return '%s({%s})' % (self.__class__.__name__, items)
如果没有对象就返回类的名字,否则返回类的名字并且返回利用most_common()方法得到类中的信息。
多集合运算
主要包括:加法(__and__),减法(__sub__),交集(__and__),并集(__or__)。需要注意的是,集合运算的结果中只会出现计数count大于0的元素!在以下的代码中也能够体现出来。其中加法和减法主要是元素数量count的加减
def __add__(self, other): '''Add counts from two counters. >>> Counter('abbb') + Counter('bcc') Counter({'b': 4, 'c': 2, 'a': 1}) ''' if not isinstance(other, Counter): return NotImplemented result = Counter() for elem, count in self.items(): newcount = count + other[elem] if newcount > 0: result[elem] = newcount for elem, count in other.items(): if elem not in self and count > 0: result[elem] = count return result def __sub__(self, other): ''' Subtract count, but keep only results with positive counts. >>> Counter('abbbc') - Counter('bccd') Counter({'b': 2, 'a': 1}) ''' if not isinstance(other, Counter): return NotImplemented result = Counter() for elem, count in self.items(): newcount = count - other[elem] if newcount > 0: result[elem] = newcount for elem, count in other.items(): if elem not in self and count < 0: result[elem] = 0 - count return result
交集主要是选相同元素中count最小的一个
def __and__(self, other): ''' Intersection is the minimum of corresponding counts. >>> Counter('abbb') & Counter('bcc') Counter({'b': 1}) ''' if not isinstance(other, Counter): return NotImplemented result = Counter() for elem, count in self.items(): other_count = other[elem] newcount = count if count < other_count else other_count if newcount > 0: result[elem] = newcount return result
并集主要是选相同元素中count最大的一个
def __or__(self, other): '''Union is the maximum of value in either of the input counters. >>> Counter('abbb') | Counter('bcc') Counter({'b': 3, 'c': 2, 'a': 1}) ''' if not isinstance(other, Counter): return NotImplemented result = Counter() for elem, count in self.items(): other_count = other[elem] newcount = other_count if count < other_count else count if newcount > 0: result[elem] = newcount for elem, count in other.items(): if elem not in self and count > 0: result[elem] = count return result
相关文章推荐
- Java之Collections工具类方法使用以及源码分析(二)
- python threadpool 源码分析以及自己封装的简易版线程池
- Java之Collections工具类方法使用以及源码分析(三)
- Python collections模块Counter类深入分析
- iOS Block源码分析系列(二)————局部变量的截获以及__block的作用和理解
- 第三百四十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—爬虫和反爬的对抗过程以及策略—scrapy架构源码分析图
- jQuery.buildFragment源码分析以及在构造jQuery对象的作用
- Java之Collections工具类方法使用以及源码分析(一)
- Java之Collections工具类方法使用以及源码分析(四)
- U-BOOT介绍以及disk模块源码分析
- 使用eclipse跟踪调试Android源码及其.classpath的作用分析
- [Python-Twisted] 源码中的interface Implement 分析
- softirq原理以及源码分析
- memset()的效率以及源码分析
- 从Java Collections源码分析迭代器模式
- 一个简单的python代理服务器源码分析
- softirq原理以及源码分析
- Combres库 学习小结以及部分源码分析
- [Python-Twisted] 协议基类源码分析。
- Python源码分析3 – 词法分析器PyTokenizer