Python collections模块
1、深入理解python中的tuple的功能
基本特性
# 可迭代 name_tuple = ('0bug', '1bug', '2bug') for name in name_tuple: print(name) # 不可变 name_tuple = ('0bug', '1bug', '2bug') name_tuple[0] = 'bug' # TypeError: 'tuple' object does not support item assignment # 不可变不是绝对的 change_tuple = (1, 2, [3, 4]) change_tuple[2][1] = 5 print(change_tuple) # (1, 2, [3, 5]) # 拆包 lcg_tuple = ('0bug', '25', '175') name, age, height = lcg_tuple print(name, age, height) lcg_tuple = ('0bug', '25', '175') name, *other = lcg_tuple print(other) # ['25', '175']
tuple比list好的地方在哪?
1,性能优化
2.线程安全
3.可以作为dict的key
# 元祖对象是可哈希的,可作为字典的key my_tuple = ('0bug','1bug') dic = {} dic[my_tuple] = 'name' print(dic) # {('0bug', '1bug'): 'name'}
4.拆包特性
如果拿c语言来类比,Tuple对应的是struct,而List对应的是array
2、namedtuple的功能详解
我们知道,class创建一个类,他的对象可以通过"."来访问对象的属性。
class User(object): def __init__(self, name, age): self.name = name self.age = age user = User('lcg', '25') print(user.name) # lcg print(user.age) # 25
namedtuple也可以创建一个类对象,继承自tuple的。
from collections import namedtuple User = namedtuple('User', ['name', 'age']) user = User('lcg', 25) print(user.name) # lcg print(user.age) # 25
namedtuple部分源码:
_class_template = """\ from builtins import property as _property, tuple as _tuple from operator import itemgetter as _itemgetter from collections import OrderedDict class {typename}(tuple): '{typename}({arg_list})' __slots__ = () _fields = {field_names!r} def __new__(_cls, {arg_list}): 'Create new instance of {typename}({arg_list})' return _tuple.__new__(_cls, ({arg_list})) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new {typename} object from a sequence or iterable' result = new(cls, iterable) if len(result) != {num_fields:d}: raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result)) return result def _replace(_self, **kwds): 'Return a new {typename} object replacing specified fields with new values' result = _self._make(map(kwds.pop, {field_names!r}, _self)) if kwds: raise ValueError('Got unexpected field names: %r' % list(kwds)) return result def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + '({repr_fmt})' % self def _asdict(self): 'Return a new OrderedDict which maps field names to their values.' return OrderedDict(zip(self._fields, self)) def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return tuple(self) {field_defs} """ _repr_template = '{name}=%r' _field_template = '''\ {name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}') ''' def namedtuple(typename, field_names, *, verbose=False, rename=False, module=None): """Returns a new subclass of tuple with named fields. >>> Point = namedtuple('Point', ['x', 'y']) >>> Point.__doc__ # docstring for the new class 'Point(x, y)' >>> p = Point(11, y=22) # instantiate with positional args or keywords >>> p[0] + p[1] # indexable like a plain tuple 33 >>> x, y = p # unpack like a regular tuple >>> x, y (11, 22) >>> p.x + p.y # fields also accessible by name 33 >>> d = p._asdict() # convert to a dictionary >>> d['x'] 11 >>> Point(**d) # convert from a dictionary Point(x=11, y=22) >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields Point(x=100, y=22) """ # Validate the field names. At the user's option, either generate an error # message or automatically replace the field name with a valid name. if isinstance(field_names, str): field_names = field_names.replace(',', ' ').split() field_names = list(map(str, field_names)) typename = str(typename) if rename: seen = set() for index, name in enumerate(field_names): if (not name.isidentifier() or _iskeyword(name) or name.startswith('_') or name in seen): field_names[index] = '_%d' % index seen.add(name) for name in [typename] + field_names: if type(name) is not str: raise TypeError('Type names and field names must be strings') if not name.isidentifier(): raise ValueError('Type names and field names must be valid ' 'identifiers: %r' % name) if _iskeyword(name): raise ValueError('Type names and field names cannot be a ' 'keyword: %r' % name) seen = set() for name in field_names: if name.startswith('_') and not rename: raise ValueError('Field names cannot start with an underscore: ' '%r' % name) if name in seen: raise ValueError('Encountered duplicate field name: %r' % name) seen.add(name) # Fill-in the class template class_definition = _class_template.format( typename = typename, field_names = tuple(field_names), num_fields = len(field_names), arg_list = repr(tuple(field_names)).replace("'", "")[1:-1], repr_fmt = ', '.join(_repr_template.format(name=name) for name in field_names), field_defs = '\n'.join(_field_template.format(index=index, name=name) for index, name in enumerate(field_names)) ) # Execute the template string in a temporary namespace and support # tracing utilities by setting a value for frame.f_globals['__name__'] namespace = dict(__name__='namedtuple_%s' % typename) exec(class_definition, namespace) result = namespace[typename] result._source = class_definition if verbose: print(result._source) # For pickling to work, the __module__ variable needs to be set to the frame # where the named tuple is created. Bypass this step in environments where # sys._getframe is not defined (Jython for example) or sys._getframe is not # defined for arguments greater than 0 (IronPython), or where the user has # specified a particular module. if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass if module is not None: result.__module__ = module return result
可以使用拆包特性传递参数
from collections import namedtuple User = namedtuple('User', ['name', 'age', 'height']) uer_tuple = ('lcg', 25) user = User(*uer_tuple, 175) print(user.name) # lcg print(user.age) # 25 user_dict = {'name': 'lcg', 'age': 25, 'height': 175} user2 = User(**user_dict) print(user2.name) # lcg
1.namedtuple也为我们提供了一个._make方法,无需考虑拆包的参数是位置参数还是关键字参数,但是一定要参数对应上
from collections import namedtuple User = namedtuple('User', ['name', 'age', 'height']) uer_tuple = ('lcg', 25, 175) user = User._make(uer_tuple) print(user.name) # lcg print(user.age) # 25 user_dict = {'name': 'lcg', 'age': 25, 'height': 175} user2 = User._make(user_dict) print(user2.name) # lcg uer_tuple = ('lcg', 25) user = User(*uer_tuple, 175) print(user.height) # 175 # 使用_make的时候,一定要参数对应上 uer_tuple = ('lcg', 25) user = User._make(uer_tuple, 175) # TypeError: 'int' object is not callable
2._asdict方法将namedtuple对象转换为OrderedDict.
from collections import namedtuple User = namedtuple('User', ['name', 'age', 'height']) uer_tuple = ('lcg', 25, 175) user = User._make(uer_tuple) user_dic = user._asdict() print(user_dic) # OrderedDict([('name', 'lcg'), ('age', 25), ('height', 175)])
3.还有一个好处是,namedtuple是继承tuple的,所有有tuple的很多特性,比如拆包
from collections import namedtuple User = namedtuple('User', ['name', 'age', 'height']) uer_tuple = ('lcg', 25, 175) user = User._make(uer_tuple) name, *other = user print(name) # lcg
3、defaultdict的功能详解
defaultdict底层调用__missing__这个魔法函数。
对一个列表总元素统计可以使用字典的setdefault方法:
users = ['0bug', '1bug', '2bug', '3bug', '0bug', '0bug', '3bug'] user_dict = {} for user in users: user_dict.setdefault(user, 0) user_dict[user] += 1 print(user_dict) # {'0bug': 3, '1bug': 1, '2bug': 1, '3bug': 2}
defaultdict也是dict的一个扩展子类,传递可调用对象为参数,如int
from collections import defaultdict default_dict_int = defaultdict(int) default_dict_str = defaultdict(str) default_dict_list = defaultdict(list) default_dict_dict = defaultdict(dict) print(default_dict_int['不存在']) # 0 print(default_dict_str['不存在']) # 这个返回空 print(default_dict_list['不存在']) # [] print(default_dict_dict['不存在']) # {}
用defaultdict统计列表中元素
from collections import defaultdict users = ['0bug', '1bug', '2bug', '3bug', '0bug', '0bug', '3bug'] default_dict = defaultdict(int) for user in users: default_dict[user] += 1 print(default_dict) # defaultdict(<class 'int'>, {'0bug': 3, '1bug': 1, '2bug': 1, '3bug': 2}) print(list(default_dict)) # {'0bug': 3, '1bug': 1, '2bug': 1, '3bug': 2}
4、deque的功能详解
deque就是双端队列
部分源码:
class deque(object): """ deque([iterable[, maxlen]]) --> deque object A list-like sequence optimized for data accesses near its endpoints. """ def append(self, *args, **kwargs): # real signature unknown """ Add an element to the right side of the deque. """ pass def appendleft(self, *args, **kwargs): # real signature unknown """ Add an element to the left side of the deque. """ pass def clear(self, *args, **kwargs): # real signature unknown """ Remove all elements from the deque. """ pass def copy(self, *args, **kwargs): # real signature unknown """ Return a shallow copy of a deque. """ pass def count(self, value): # real signature unknown; restored from __doc__ """ D.count(value) -> integer -- return number of occurrences of value """ return 0 def extend(self, *args, **kwargs): # real signature unknown """ Extend the right side of the deque with elements from the iterable """ pass def extendleft(self, *args, **kwargs): # real signature unknown """ Extend the left side of the deque with elements from the iterable """ pass def index(self, value, start=None, stop=None): # real signature unknown; restored from __doc__ """ D.index(value, [start, [stop]]) -> integer -- return first index of value. Raises ValueError if the value is not present. """ return 0 def insert(self, index, p_object): # real signature unknown; restored from __doc__ """ D.insert(index, object) -- insert object before index """ pass def pop(self, *args, **kwargs): # real signature unknown """ Remove and return the rightmost element. """ pass def popleft(self, *args, **kwargs): # real signature unknown """ Remove and return the leftmost element. """ pass def remove(self, value): # real signature unknown; restored from __doc__ """ D.remove(value) -- remove first occurrence of value. """ pass def reverse(self): # real signature unknown; restored from __doc__ """ D.reverse() -- reverse *IN PLACE* """ pass def rotate(self, *args, **kwargs): # real signature unknown """ Rotate the deque n steps to the right (default n=1). If n is negative, rotates left. """ pass def __add__(self, *args, **kwargs): # real signature unknown """ Return self+value. """ pass def __bool__(self, *args, **kwargs): # real signature unknown """ self != 0 """ pass def __contains__(self, *args, **kwargs): # real signature unknown """ Return key in self. """ pass def __copy__(self, *args, **kwargs): # real signature unknown """ Return a shallow copy of a deque. """ pass def __delitem__(self, *args, **kwargs): # real signature unknown """ Delete self[key]. """ pass def __eq__(self, *args, **kwargs): # real signature unknown """ Return self==value. """ pass def __getattribute__(self, *args, **kwargs): # real signature unknown """ Return getattr(self, name). """ pass def __getitem__(self, *args, **kwargs): # real signature unknown """ Return self[key]. """ pass def __ge__(self, *args, **kwargs): # real signature unknown """ Return self>=value. """ pass def __gt__(self, *args, **kwargs): # real signature unknown """ Return self>value. """ pass def __iadd__(self, *args, **kwargs): # real signature unknown """ Implement self+=value. """ pass def __imul__(self, *args, **kwargs): # real signature unknown """ Implement self*=value. """ pass def __init__(self, iterable=(), maxlen=None): # known case of _collections.deque.__init__ """ deque([iterable[, maxlen]]) --> deque object A list-like sequence optimized for data accesses near its endpoints. # (copied from class doc) """ pass def __iter__(self, *args, **kwargs): # real signature unknown """ Implement iter(self). """ pass def __len__(self, *args, **kwargs): # real signature unknown """ Return len(self). """ pass def __le__(self, *args, **kwargs): # real signature unknown """ Return self<=value. """ pass def __lt__(self, *args, **kwargs): # real signature unknown """ Return self<value. """ pass def __mul__(self, *args, **kwargs): # real signature unknown """ Return self*value.n """ pass @staticmethod # known case of __new__ def __new__(*args, **kwargs): # real signature unknown """ Create and return a new object. See help(type) for accurate signature. """ pass def __ne__(self, *args, **kwargs): # real signature unknown """ Return self!=value. """ pass def __reduce__(self, *args, **kwargs): # real signature unknown """ Return state information for pickling. """ pass def __repr__(self, *args, **kwargs): # real signature unknown """ Return repr(self). """ pass def __reversed__(self): # real signature unknown; restored from __doc__ """ D.__reversed__() -- return a reverse iterator over the deque """ pass def __rmul__(self, *args, **kwargs): # real signature unknown """ Return self*value. """ pass def __setitem__(self, *args, **kwargs): # real signature unknown """ Set self[key] to value. """ pass def __sizeof__(self): # real signature unknown; restored from __doc__ """ D.__sizeof__() -- size of D in memory, in bytes """ pass maxlen = property(lambda self: object(), lambda self, v: None, lambda self: None) # default """maximum size of a deque or None if unbounded""" __hash__ = None
append、appendleft ,将元素插入到尾部 / 头部
from collections import deque user_deque = deque(['0bug', '1bug', '2bug']) print(user_deque) # deque(['0bug', '1bug', '2bug']) user_deque.append('3bug') user_deque.appendleft('-1bug') print(user_deque) # deque(['-1bug', '0bug', '1bug', '2bug', '3bug'])
clear 清空数据
from collections import deque user_deque = deque(['0bug', '1bug', '2bug']) user_deque.clear() print(user_deque) # deque([])
copy 浅拷贝
from collections import deque user_deque = deque(['0bug', ['1bug', '2bug']]) user_deque2 = user_deque.copy() print(id(user_deque), id(user_deque2)) # 2112944554992 2112944556968 user_deque2[1][0] = '3bug' print(user_deque) # deque(['0bug', ['3bug', '2bug']]) print(user_deque2) # deque(['0bug', ['3bug', '2bug']]) # 浅拷贝只拷贝一层,所以user_deque2修改第二层会影响user_deque
深拷贝需要使用copy模块里的deepcopy方法
from collections import deque import copy user_deque = deque(['0bug', ['1bug', '2bug']]) user_deque2 = copy.deepcopy(user_deque) print(id(user_deque), id(user_deque2)) # 1642030214472 1642030214368 user_deque2[1][0] = '3bug' print(user_deque) # deque(['0bug', ['1bug', '2bug']]) print(user_deque2) # deque(['0bug', ['3bug', '2bug']])
count 计数
from collections import deque user_deque = deque(['0bug', '1bug', '2bug','0bug']) print(user_deque.count('0bug')) # 2
extend二合一,动态扩容(无返回值而是扩容!)
from collections import deque user_deque1 = deque(['0bug', '1bug']) user_deque2 = deque(['2bug', '3bug']) user_deque1.extend(user_deque2) print(user_deque1) # deque(['0bug', '1bug', '2bug', '3bug'])
extendleft 左扩容
from collections import deque user_deque1 = deque(['0bug', '1bug']) user_deque2 = deque(['2bug', '3bug']) user_deque1.extendleft(user_deque2) print(user_deque1) # deque(['3bug', '2bug', '0bug', '1bug'])
index查找索引,找不到就抛出异常
from collections import deque user_deque = deque(['0bug', '1bug']) print(user_deque.index('0bug')) # 0 print(user_deque.index('0xbug')) # ValueError: '0xbug' is not in deque
insert 指定位置插入元素
from collections import deque user_deque = deque(['0bug', '1bug']) user_deque.insert(0, '0xbug') print(user_deque) # deque(['0xbug', '0bug', '1bug'])
pop, popleft 弹出尾部 头部元素(有返回值)
from collections import deque user_deque = deque(['0bug', '1bug', '2bug']) user_pop = user_deque.pop() print(user_pop) # 2bug print(user_deque) # deque(['0bug', '1bug']) user_deque.popleft() print(user_deque) # deque(['1bug'])
remove删除某个存在的元素,不存在就抛出异常,无返回值
from collections import deque user_deque = deque(['0bug', '1bug', '2bug']) user_deque.remove('0bug') print(user_deque) # deque(['1bug', '2bug'])
reverse 原地反转(无返回值)
from collections import deque user_deque = deque(['0bug', '1bug', '2bug']) user_deque.reverse() print(user_deque) # deque(['2bug', '1bug', '0bug'])
deque还有一些魔法函数,其实魔法函数也就是python的协议,是python解释器直接调用的。
deque的应用场景
from queue import Queue 这里的Queue就是通过双端队列deque来实现的
Queue部分源码
class Queue: '''Create a queue object with a given maximum size. If maxsize is <= 0, the queue size is infinite. ''' def __init__(self, maxsize=0): self.maxsize = maxsize self._init(maxsize) # mutex must be held whenever the queue is mutating. All methods # that acquire mutex must release it before returning. mutex # is shared between the three conditions, so acquiring and # releasing the conditions also acquires and releases mutex. self.mutex = threading.Lock() # Notify not_empty whenever an item is added to the queue; a # thread waiting to get is notified then. self.not_empty = threading.Condition(self.mutex) # Notify not_full whenever an item is removed from the queue; # a thread waiting to put is notified then. self.not_full = threading.Condition(self.mutex) # Notify all_tasks_done whenever the number of unfinished tasks # drops to zero; thread waiting to join() is notified to resume self.all_tasks_done = threading.Condition(self.mutex) self.unfinished_tasks = 0 def task_done(self): '''Indicate that a formerly enqueued task is complete. Used by Queue consumer threads. For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing on the task is complete. If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue). Raises a ValueError if called more times than there were items placed in the queue. ''' with self.all_tasks_done: unfinished = self.unfinished_tasks - 1 if unfinished <= 0: if unfinished < 0: raise ValueError('task_done() called too many times') self.all_tasks_done.notify_all() self.unfinished_tasks = unfinished def join(self): '''Blocks until all items in the Queue have been gotten and processed. The count of unfinished tasks goes up whenever an item is added to the queue. The count goes down whenever a consumer thread calls task_done() to indicate the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, join() unblocks. ''' with self.all_tasks_done: while self.unfinished_tasks: self.all_tasks_done.wait() def qsize(self): '''Return the approximate size of the queue (not reliable!).''' with self.mutex: return self._qsize() def empty(self): '''Return True if the queue is empty, False otherwise (not reliable!). This method is likely to be removed at some point. Use qsize() == 0 as a direct substitute, but be aware that either approach risks a race condition where a queue can grow before the result of empty() or qsize() can be used. To create code that needs to wait for all queued tasks to be completed, the preferred technique is to use the join() method. ''' with self.mutex: return not self._qsize() def full(self): '''Return True if the queue is full, False otherwise (not reliable!). This method is likely to be removed at some point. Use qsize() >= n as a direct substitute, but be aware that either approach risks a race condition where a queue can shrink before the result of full() or qsize() can be used. ''' with self.mutex: return 0 < self.maxsize <= self._qsize() def put(self, item, block=True, timeout=None): '''Put an item into the queue. If optional args 'block' is true and 'timeout' is None (the default), block if necessary until a free slot is available. If 'timeout' is a non-negative number, it blocks at most 'timeout' seconds and raises the Full exception if no free slot was available within that time. Otherwise ('block' is false), put an item on the queue if a free slot is immediately available, else raise the Full exception ('timeout' is ignored in that case). ''' with self.not_full: if self.maxsize > 0: if not block: if self._qsize() >= self.maxsize: raise Full elif timeout is None: while self._qsize() >= self.maxsize: self.not_full.wait() elif timeout < 0: raise ValueError("'timeout' must be a non-negative number") else: endtime = time() + timeout while self._qsize() >= self.maxsize: remaining = endtime - time() if remaining <= 0.0: raise Full self.not_full.wait(remaining) self._put(item) self.unfinished_tasks += 1 self.not_empty.notify() def get(self, block=True, timeout=None): '''Remove and return an item from the queue. If optional args 'block' is true and 'timeout' is None (the default), block if necessary until an item is available. If 'timeout' is a non-negative number, it blocks at most 'timeout' seconds and raises the Empty exception if no item was available within that time. Otherwise ('block' is false), return an item if one is immediately available, else raise the Empty exception ('timeout' is ignored in that case). ''' with self.not_empty: if not block: if not self._qsize(): raise Empty elif timeout is None: while not self._qsize(): self.not_empty.wait() elif timeout < 0: raise ValueError("'timeout' must be a non-negative number") else: endtime = time() + timeout while not self._qsize(): remaining = endtime - time() if remaining <= 0.0: raise Empty self.not_empty.wait(remaining) item = self._get() self.not_full.notify() return item def put_nowait(self, item): '''Put an item into the queue without blocking. Only enqueue the item if a free slot is immediately available. Otherwise raise the Full exception. ''' return self.put(item, block=False) def get_nowait(self): '''Remove and return an item from the queue without blocking. Only get an item if one is immediately available. Otherwise raise the Empty exception. ''' return self.get(block=False) # Override these methods to implement other queue organizations # (e.g. stack or priority queue). # These will only be called with appropriate locks held # Initialize the queue representation def _init(self, maxsize): self.queue = deque() def _qsize(self): return len(self.queue) # Put a new item in the queue def _put(self, item): self.queue.append(item) # Get an item from the queue def _get(self): return self.queue.popleft()
还有一个很重要的特性:deque是GIL来保护的,是线程安全的list是非线程安全的。
5、Counter功能详解
Counter是dict的一个子类。一般用来做统计
Counter部分源码:
class Counter(dict): '''Dict subclass for counting hashable items. Sometimes called a bag or multiset. Elements are stored as dictionary keys and their counts are stored as dictionary values. >>> c = Counter('abcdeabcdabcaba') # count elements from a string >>> c.most_common(3) # three most common elements [('a', 5), ('b', 4), ('c', 3)] >>> sorted(c) # list all unique elements ['a', 'b', 'c', 'd', 'e'] >>> ''.join(sorted(c.elements())) # list elements with repetitions 'aaaaabbbbcccdde' >>> sum(c.values()) # total of all counts 15 >>> c['a'] # count of letter 'a' 5 >>> for elem in 'shazam': # update counts from an iterable ... c[elem] += 1 # by adding 1 to each element's count >>> c['a'] # now there are seven 'a' 7 >>> del c['b'] # remove all 'b' >>> c['b'] # now there are zero 'b' 0 >>> d = Counter('simsalabim') # make another counter >>> c.update(d) # add in the second counter >>> c['a'] # now there are nine 'a' 9 >>> c.clear() # empty the counter >>> c Counter() Note: If a count is set to zero or reduced to zero, it will remain in the counter until the entry is deleted or the counter is cleared: >>> c = Counter('aaabbc') >>> c['b'] -= 2 # reduce the count of 'b' by two >>> c.most_common() # 'b' is still in, but its count is zero [('a', 3), ('c', 1), ('b', 0)] ''' # References: # http://en.wikipedia.org/wiki/Multiset # http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html # http://www.demo2s.com/Tutorial/Cpp/0380__set-multiset/Catalog0380__set-multiset.htm # http://code.activestate.com/recipes/259174/ # Knuth, TAOCP Vol. II section 4.6.3 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 = args if len(args) > 1: raise TypeError('expected at most 1 arguments, got %d' % len(args)) super(Counter, self).__init__() self.update(*args, **kwds) 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 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.items(), key=_itemgetter(1), reverse=True) return _heapq.nlargest(n, self.items(), key=_itemgetter(1)) 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.items())) # Override dict methods where necessary @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.') 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 = 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) def subtract(*args, **kwds): '''Like dict.update() but subtracts counts instead of replacing them. Counts can be reduced below zero. Both the inputs and outputs are allowed to contain zero and negative counts. Source can be an iterable, a dictionary, or another Counter instance. >>> c = Counter('which') >>> c.subtract('witch') # subtract elements from another iterable >>> c.subtract(Counter('watch')) # subtract elements from another counter >>> c['h'] # 2 in which, minus 1 in witch, minus 1 in watch 0 >>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch -1 ''' if not args: raise TypeError("descriptor 'subtract' 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: self_get = self.get if isinstance(iterable, Mapping): for elem, count in iterable.items(): self[elem] = self_get(elem, 0) - count else: for elem in iterable: self[elem] = self_get(elem, 0) - 1 if kwds: self.subtract(kwds) def copy(self): 'Return a shallow copy.' return self.__class__(self) def __reduce__(self): return self.__class__, (dict(self),) def __delitem__(self, elem): 'Like dict.__delitem__() but does not raise KeyError for missing values.' if elem in self: super().__delitem__(elem) def __repr__(self): if not self: return '%s()' % self.__class__.__name__ try: items = ', '.join(map('%r: %r'.__mod__, self.most_common())) return '%s({%s})' % (self.__class__.__name__, items) except TypeError: # handle case where values are not orderable return '{0}({1!r})'.format(self.__class__.__name__, dict(self)) # Multiset-style mathematical operations discussed in: # Knuth TAOCP Volume II section 4.6.3 exercise 19 # and at http://en.wikipedia.org/wiki/Multiset # # Outputs guaranteed to only include positive counts. # # To strip negative and zero counts, add-in an empty counter: # c += Counter() 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 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 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 def __pos__(self): 'Adds an empty counter, effectively stripping negative and zero counts' result = Counter() for elem, count in self.items(): if count > 0: result[elem] = count return result def __neg__(self): '''Subtracts from an empty counter. Strips positive and zero counts, and flips the sign on negative counts. ''' result = Counter() for elem, count in self.items(): if count < 0: result[elem] = 0 - count return result def _keep_positive(self): '''Internal method to strip elements with a negative or zero count''' nonpositive = [elem for elem, count in self.items() if not count > 0] for elem in nonpositive: del self[elem] return self def __iadd__(self, other): '''Inplace add from another counter, keeping only positive counts. >>> c = Counter('abbb') >>> c += Counter('bcc') >>> c Counter({'b': 4, 'c': 2, 'a': 1}) ''' for elem, count in other.items(): self[elem] += count return self._keep_positive() def __isub__(self, other): '''Inplace subtract counter, but keep only results with positive counts. >>> c = Counter('abbbc') >>> c -= Counter('bccd') >>> c Counter({'b': 2, 'a': 1}) ''' for elem, count in other.items(): self[elem] -= count return self._keep_positive() def __ior__(self, other): '''Inplace union is the maximum of value from either counter. >>> c = Counter('abbb') >>> c |= Counter('bcc') >>> c Counter({'b': 3, 'c': 2, 'a': 1}) ''' for elem, other_count in other.items(): count = self[elem] if other_count > count: self[elem] = other_count return self._keep_positive() def __iand__(self, other): '''Inplace intersection is the minimum of corresponding counts. >>> c = Counter('abbb') >>> c &= Counter('bcc') >>> c Counter({'b': 1}) ''' for elem, count in self.items(): other_count = other[elem] if other_count < count: self[elem] = other_count return self._keep_positive()
统计列表
from collections import Counter li = ['a', 'a', 'b', 'c', 'c', 'c', 'd'] count = Counter(li) print(count) # Counter({'c': 3, 'a': 2, 'b': 1, 'd': 1})
统计字符串
from collections import Counter count = Counter('asdfgasddfghasdg') print(count) # Counter({'d': 4, 'a': 3, 's': 3, 'g': 3, 'f': 2, 'h': 1})
统计英文文章的10个高频单词
import re from collections import Counter from pprint import pprint with open('test.txt') as f: txt = f.read() c = Counter(re.split('\W+', txt)) res = c.most_common(10) pprint(res)
update合并统计,参数是一个可迭代对象,Counter是dict的子类,所有也可以传一个Counter对象
from collections import Counter count = Counter('abcaab') count.update('ddddeccc') print(count) # Counter({'c': 4, 'd': 4, 'a': 3, 'b': 2, 'e': 1}) count2 = Counter('abcaab') count3 = Counter('ddddeccc') count2.update(count3) print(count2) # Counter({'c': 4, 'd': 4, 'a': 3, 'b': 2, 'e': 1})
most_common(n)统计高频前n个元素,用来解决top n的问题
from collections import Counter count = Counter('abcaab') print(count.most_common(2)) # [('a', 3), ('b', 2)]
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.items(), key=_itemgetter(1), reverse=True) return _heapq.nlargest(n, self.items(), key=_itemgetter(1)) # _heapq是python中的一个数据结构:堆
6、OrderedDict功能详解
OrderedDict是dict的一个子类
OrderedDict部分源码:
class OrderedDict(dict): 'Dictionary that remembers insertion order' # An inherited dict maps keys to values. # The inherited dict provides __getitem__, __len__, __contains__, and get. # The remaining methods are order-aware. # Big-O running times for all methods are the same as regular dictionaries. # The internal self.__map dict maps keys to links in a doubly linked list. # The circular doubly linked list starts and ends with a sentinel element. # The sentinel element never gets deleted (this simplifies the algorithm). # The sentinel is in self.__hardroot with a weakref proxy in self.__root. # The prev links are weakref proxies (to prevent circular references). # Individual links are kept alive by the hard reference in self.__map. # Those hard references disappear when a key is deleted from an OrderedDict. def __init__(*args, **kwds): '''Initialize an ordered dictionary. The signature is the same as regular dictionaries. Keyword argument order is preserved. ''' if not args: raise TypeError("descriptor '__init__' of 'OrderedDict' object " "needs an argument") self, *args = args if len(args) > 1: raise TypeError('expected at most 1 arguments, got %d' % len(args)) try: self.__root except AttributeError: self.__hardroot = _Link() self.__root = root = _proxy(self.__hardroot) root.prev = root.next = root self.__map = {} self.__update(*args, **kwds) def __setitem__(self, key, value, dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link): 'od.__setitem__(i, y) <==> od[i]=y' # Setting a new item creates a new link at the end of the linked list, # and the inherited dictionary is updated with the new key/value pair. if key not in self: self.__map[key] = link = Link() root = self.__root last = root.prev link.prev, link.next, link.key = last, root, key last.next = link root.prev = proxy(link) dict_setitem(self, key, value) def __delitem__(self, key, dict_delitem=dict.__delitem__): 'od.__delitem__(y) <==> del od[y]' # Deleting an existing item uses self.__map to find the link which gets # removed by updating the links in the predecessor and successor nodes. dict_delitem(self, key) link = self.__map.pop(key) link_prev = link.prev link_next = link.next link_prev.next = link_next link_next.prev = link_prev link.prev = None link.next = None def __iter__(self): 'od.__iter__() <==> iter(od)' # Traverse the linked list in order. root = self.__root curr = root.next while curr is not root: yield curr.key curr = curr.next def __reversed__(self): 'od.__reversed__() <==> reversed(od)' # Traverse the linked list in reverse order. root = self.__root curr = root.prev while curr is not root: yield curr.key curr = curr.prev def clear(self): 'od.clear() -> None. Remove all items from od.' root = self.__root root.prev = root.next = root self.__map.clear() dict.clear(self) def popitem(self, last=True): '''Remove and return a (key, value) pair from the dictionary. Pairs are returned in LIFO order if last is true or FIFO order if false. ''' if not self: raise KeyError('dictionary is empty') root = self.__root if last: link = root.prev link_prev = link.prev link_prev.next = root root.prev = link_prev else: link = root.next link_next = link.next root.next = link_next link_next.prev = root key = link.key del self.__map[key] value = dict.pop(self, key) return key, value def move_to_end(self, key, last=True): '''Move an existing element to the end (or beginning if last==False). Raises KeyError if the element does not exist. When last=True, acts like a fast version of self[key]=self.pop(key). ''' link = self.__map[key] link_prev = link.prev link_next = link.next soft_link = link_next.prev link_prev.next = link_next link_next.prev = link_prev root = self.__root if last: last = root.prev link.prev = last link.next = root root.prev = soft_link last.next = link else: first = root.next link.prev = root link.next = first first.prev = soft_link root.next = link def __sizeof__(self): sizeof = _sys.getsizeof n = len(self) + 1 # number of links including root size = sizeof(self.__dict__) # instance dictionary size += sizeof(self.__map) * 2 # internal dict and inherited dict size += sizeof(self.__hardroot) * n # link objects size += sizeof(self.__root) * n # proxy objects return size update = __update = MutableMapping.update def keys(self): "D.keys() -> a set-like object providing a view on D's keys" return _OrderedDictKeysView(self) def items(self): "D.items() -> a set-like object providing a view on D's items" return _OrderedDictItemsView(self) def values(self): "D.values() -> an object providing a view on D's values" return _OrderedDictValuesView(self) __ne__ = MutableMapping.__ne__ __marker = object() def pop(self, key, default=__marker): '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. If key is not found, d is returned if given, otherwise KeyError is raised. ''' if key in self: result = self[key] del self[key] return result if default is self.__marker: raise KeyError(key) return default def setdefault(self, key, default=None): 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' if key in self: return self[key] self[key] = default return default @_recursive_repr() def __repr__(self): 'od.__repr__() <==> repr(od)' if not self: return '%s()' % (self.__class__.__name__,) return '%s(%r)' % (self.__class__.__name__, list(self.items())) def __reduce__(self): 'Return state information for pickling' inst_dict = vars(self).copy() for k in vars(OrderedDict()): inst_dict.pop(k, None) return self.__class__, (), inst_dict or None, None, iter(self.items()) def copy(self): 'od.copy() -> a shallow copy of od' return self.__class__(self) @classmethod def fromkeys(cls, iterable, value=None): '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S. If not specified, the value defaults to None. ''' self = cls() for key in iterable: self[key] = value return self def __eq__(self, other): '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive while comparison to a regular mapping is order-insensitive. ''' if isinstance(other, OrderedDict): return dict.__eq__(self, other) and all(map(_eq, self, other)) return dict.__eq__(self, other)
OrderedDict是有序的,有序体现在,先添加的排在前面,后添加的排在后面
from collections import OrderedDict user_dict = OrderedDict() user_dict['b'] = '0xbug' user_dict['a'] = '0bug' user_dict['c'] = '1bug' print(user_dict) # OrderedDict([('b', '0xbug'), ('a', '0bug'), ('c', '1bug')])
popitem 弹出最后一个组
from collections import OrderedDict user_dict = OrderedDict() user_dict['b'] = '0xbug' user_dict['a'] = '0bug' user_dict['c'] = '1bug' print(user_dict) # OrderedDict([('b', '0xbug'), ('a', '0bug'), ('c', '1bug')]) print(user_dict.popitem()) # ('c', '1bug') print(user_dict) # OrderedDict([('b', '0xbug'), ('a', '0bug')])
pop弹出指定元素,必须写参数
from collections import OrderedDict user_dict = OrderedDict() user_dict['b'] = '0xbug' user_dict['a'] = '0bug' user_dict['c'] = '1bug' print(user_dict) # OrderedDict([('b', '0xbug'), ('a', '0bug'), ('c', '1bug')]) print(user_dict.pop('a')) # 0bug print(user_dict) # OrderedDict([('b', '0xbug'), ('c', '1bug')])
move_to_end将指定元素移至最后
from collections import OrderedDict user_dict = OrderedDict() user_dict['b'] = '0xbug' user_dict['a'] = '0bug' user_dict['c'] = '1bug' print(user_dict) # OrderedDict([('b', '0xbug'), ('a', '0bug'), ('c', '1bug')]) print(user_dict.move_to_end('a')) # 0bug print(user_dict) # OrderedDict([('b', '0xbug'), ('c', '1bug'), ('a', '0bug')])
7、ChainMap功能详解
ChainMap可以让我们访问多个字典像访问一个字典一样进行操作,实质是指向关系而不是复制。如果key值重复,循环遍历的时候只算第一个
from collections import ChainMap user_dict1 = {'a': '0bug', 'b': '1bug'} user_dict2 = {'b': '2bug', 'c': '3bug', 'd': '4bug'} new_dict = ChainMap(user_dict1, user_dict2) print(new_dict) # ChainMap({'a': '0bug', 'b': '1bug'}, {'b': '2bug', 'c': '3bug', 'd': '4bug'}) for key, value in new_dict.items(): print(key, value) """ c 3bug d 4bug b 1bug a 0bug """
maps返回列表形式组合
from collections import ChainMap user_dict1 = {'a': '0bug', 'b': '1bug'} user_dict2 = {'b': '2bug', 'c': '3bug', 'd': '4bug'} new_dict = ChainMap(user_dict1, user_dict2) print(new_dict.maps) # [{'a': '0bug', 'b': '1bug'}, {'b': '2bug', 'c': '3bug', 'd': '4bug'}] new_dict.maps[0]['a'] = 'aaaa' print(new_dict) # ChainMap({'a': 'aaaa', 'b': '1bug'}, {'b': '2bug', 'c': '3bug', 'd': '4bug'})