[Python] collections — High-performance container datatypes

python中基础的数据结构除了str, int, float等超简单类型外,还提供了类似于C++中STL库里的一些较高级的数据结构,如tuple, list, dict和set。

而collections则是Python中内建的一个集合模块,提供了一些更加高级的集合类。

namedtuple (factory function for creating tuple subclasses with named fields) 作为一个函数,可以创建自定义的tuple对象,可以规定元素的数目和属性

deque (list-like container with fast appends and pops on either end) 双向链表,支持快速地在链表的两端进行插入和删除操作

Counter (dict subclass for counting hashable objects) 是一个简单的计数器,也是dict的子类

OrderedDict (dict subclass that remembers the order entries were added) 可以按照插入的顺利排列key值(可以实现一个FIFO的字典)

defaultdict (dict subclass that calls a factory function to supply missing values) 当引用的key不存在时可以返回默认值,而不是抛出KeyError


1. namedtuple

使用方法:
namedtuple(typename, field_names[, verbose=False][, rename=False])
其中typename表示自定义的类名,field_names则表示元素的属性名称列表,这两个参数是必选的。
可选参数rename,当取默认值False时,属性名称列表中出现同名或者采用关键字def的情况下会报错,而设置为True的时候,那些冲突的名字会被重命名为“_+数字”的形式、
可选参数verbose,当设置为True的时候,会在自定义类刚创建时输出其相关的所有信息,如下所示:

>>> Point = namedtuple('Point', ['x', 'y', 'y'], verbose = True, rename = True)
class Point(tuple):
    'Point(x, y, _2)'

    __slots__ = ()

    _fields = ('x', 'y', '_2')

    def __new__(_cls, x, y, _2):
        'Create new instance of Point(x, y, _2)'
        return _tuple.__new__(_cls, (x, y, _2))

    @classmethod
    def _make(cls, iterable, new=tuple.__new__, len=len):
        'Make a new Point object from a sequence or iterable'
        result = new(cls, iterable)
        if len(result) != 3:
            raise TypeError('Expected 3 arguments, got %d' % len(result))
        return result

    def __repr__(self):
        'Return a nicely formatted representation string'
        return 'Point(x=%r, y=%r, _2=%r)' % self

    def _asdict(self):
        'Return a new OrderedDict which maps field names to their values'
        return OrderedDict(zip(self._fields, self))

    def _replace(_self, **kwds):
        'Return a new Point object replacing specified fields with new values'
        result = _self._make(map(kwds.pop, ('x', 'y', '_2'), _self))
        if kwds:
            raise ValueError('Got unexpected field names: %r' % kwds.keys())
        return result

    def __getnewargs__(self):
        'Return self as a plain tuple.  Used by copy and pickle.'
        return tuple(self)

    __dict__ = _property(_asdict)

    def __getstate__(self):
        'Exclude the OrderedDict from pickling'
        pass

    x = _property(_itemgetter(0), doc='Alias for field number 0')

    y = _property(_itemgetter(1), doc='Alias for field number 1')

    _2 = _property(_itemgetter(2), doc='Alias for field number 2')


namedtuple 具有tuple的不变性,实例化一个对象后不能改变其属性的值,只能访问。

>>> from collections import namedtuple
>>> Circle = namedtuple('Circle', ['x', 'y', 'r'])
>>> Circle
<class '__main__.Circle'>
>>> c = Circle(1, 2, 3)
>>> c
Circle(x=1, y=2, r=3)
>>> c.x = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> c.x
1


一个重要的应用,将结构化的csv文件中的所有记录读出后利用自定义的记录结果存储
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')

import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
    print emp.name, emp.title

>>> RecordName = namedtuple('RecordName', 'ID, Title, Charge_State, Mass')
>>> for rcd in map(RecordName._make, csv.reader(open("test.csv", "rb") ) ):
...     print rcd.Title
...
ecoli_4ug_10HCD.4018.4018.9.1.dta
ecoli_4ug_10HCD.4540.4540.5.0.dta
ecoli_4ug_10HCD.3920.3920.6.1.dta

需要注意的是虽然写csv文件时支持列名含有空格,仅采用“,”分隔,但是在定义namedtuple时逗号和空格都作为分隔,下面一个例子本以为只有4个属性,可实际上有5个!
>>> RecordName = namedtuple('RecordName', 'ID, Title, Charge State, Mass')
>>> for rcd in map(RecordName._make, csv.reader(open("test.csv", "rb") ) ):
...     print rcd.Title
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 17, in _make
TypeError: Expected 5 arguments, got 4


另外,它还支持将查询sqlite3数据库返回的结果采用相同的方式存储。

它提供的额外三个函数和一个属性:
_make(iterable),通过已经存在的一个实例或者序列创建一个新的实例

>>> Point = namedtuple('Point', ['x', 'y'])
>>> t = [1, 2]
>>> Point._make(t)
Point(x=1, y=2)
>>> 
>>> t = [[1, 2], [3, 4], [5, 6]]
>>> map(Point._make, t)
[Point(x=1, y=2), Point(x=3, y=4), Point(x=5, y=6)]

其中,map函数func作用于给定序列的每个元素,并用一个列表来提供返回值。

_asdict(),将一个实例转化成一个OrderedDict,其中属性名为key,实例值为value

>>> p = Point(11, 12)
>>> p._asdict()
OrderedDict([('x', 11), ('y', 12)])

_replace(kwargs),替换其中某个或者某些属性值,返回一个新的实例

>>> p._replace(x=33)
Point(x=33, y=12)
>>> p._replace(x=33, y=44)
Point(x=33, y=44)

_fields,返回一个字符串的元组,其元素为属性名,可以采用实例调用,也可以直接用类名调用
可以用一些已经存在的元组属性来定义一个新的元祖

>>> p._fields
('x', 'y')
>>> NewPoint = namedtuple('NewPoint', Point._fields + ['z']) #不能用元组属性与列表直接相加
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "list") to tuple
>>> Add = namedtuple('Add', 'z')
>>> NewPoint = namedtuple('NewPoint', Point._fields + Add._fields)
>>> NewPoint(1, 2, 3)
NewPoint(x=1, y=2, z=3)


2. deque
deque([iterable[, maxlen]])
与list相比,deque作为双向链表,在左右两端的删除和插入的时间复杂度均为O(1),它也支持采用[]进行读取(时间复杂度却是O(n)的),也可以采用负下标表示倒数第几个元素,适合用于栈和队列。

它支持的函数主要有:

append(x),向deque的右侧添加元素

appendleft(x),向deque的左侧添加元素

clear(),清空所有元素

count(x),计数元素为x的项的数目

extend(iterable), 向右侧添加迭代器中的元素

extendleft(iterable), 向左侧添加迭代器中的元素,注意,在deque中添加的元素顺序与迭代器中的相反

pop(),从右侧删除一个元素并返回,如果deque为空则出现IndexError

popleft(),从左侧删除一个元素并返回,如果deque为空则出现IndexError

remove(value),删除从左往右看value第一次出现的那个,如果没有则出现ValueError

reverse(),就地逆序,返回None

rotate(n),旋转元素,参数n表示右移n个位置,如果n为负数,则是左移

>>> d = deque('abc')     #deque(['a', 'b', 'c'])
>>> d.append('d')          #deque(['a', 'b', 'c', 'd'])
>>> d.appendleft('z')     #deque(['z', 'a', 'b', 'c', 'd'])
>>> d.pop()                  #deque(['z', 'a', 'b', 'c'])
'd'
>>> d.popleft()          #deque(['a', 'b', 'c'])
'z'
>>> d[0]
'a'
>>> d[-1]
'c'
>>> list(d)
['a', 'b', 'c']
>>> list(reversed(d))
['c', 'b', 'a']
>>> d.extend('def')          #deque(['a', 'b', 'c', 'd', 'e', 'f'])
>>> d.extendleft('xyz')     #deque(['z', 'y', 'x', 'a', 'b', 'c', 'd', 'e', 'f'])
>>> d.rotate(3)               #deque(['d', 'e', 'f', 'z', 'y', 'x', 'a', 'b', 'c'])
>>> d.rotate(-3)             #deque(['z', 'y', 'x', 'a', 'b', 'c', 'd', 'e', 'f'])
>>> d.clear()                  #deque([])
>>> d.pop()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: pop from an empty deque
>>> d = deque('abcadc')
>>> d.count('a')
2
>>> d.remove('a')     #deque([ 'b', 'c', 'a', 'd', 'c'])

append和extend的区别是前者插入一个元素,后者插入迭代器中的所有元素,尤其是处理字符串的时候需要注意,因为它既可以作为一个元素插入,也可以作为一个迭代器。appendleft和extendleft也一样。
关于extendleft逆序的问题,可以理解为对于迭代器中的每个元素,分别取出后采用appendletf插入到deque的头部,类似入栈,所以是逆序。

>>> d = deque('abc')
>>> d.appendleft('XYZ')
>>> d
deque(['XYZ', 'a', 'b', 'c'])
>>> d.extendleft('XYZ')
>>> d
deque(['Z', 'Y', 'X', 'XYZ', 'A', 'a', 'b', 'c', 'd'])

3. Counter

Counter([iterable-or-mapping])
Counter 其实是一个defaultdict,计数的元素为key,数目为value,当查询不存在的key时返回0,而不是出错。

对单词列表进行简单的计数:
>>> from collections import Counter
>>> cnt = Counter()
>>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']: cnt[word] += 1
...
>>> cnt
Counter({'blue': 3, 'red': 2, 'green': 1})

初始化的若干种方法:
>>> c = Counter()                                   #Counter()
>>> c = Counter('gallahad')                    #Counter({'a': 3, 'l': 2, 'h': 1, 'g': 1, 'd': 1})
>>> c = Counter({'red':2, 'blue':1})          #Counter({'red': 2, 'blue': 1})
>>> c = Counter(cats = 4, dogs = 8)     #Counter({'dogs': 8, 'cats': 4})

与dict一样的访问方法,把元素置0和把元素删除并不相同,Counter对应的计数值还可以为负。
>>> c = Counter(cats = 4, dogs = 8)     #Counter({'dogs': 8, 'cats': 4})
>>> c['rabbit']
0
>>> c['cats'] = 0     #Counter({'dogs': 8, 'cats': 0})
>>> del c['cats']     #Counter({'dogs': 8})

elements() 函数可以将所有的计数元素转换为迭代器,其中每个元素会根据其计数重复多次,如果是负数或者0,则该元素不会出现。
>>> c['dogs'] = 3
>>> c.elements()
<itertools.chain object at 0x0000000002ABBCF8>
>>> list(c.elements())
['dogs', 'dogs', 'dogs']
>>> c = Counter({'cats' : -1})
>>> list(c.elements())
[]

most_common([n]) 函数返回技术由大到小排名的前n名,如果有并列的情况不会全部都输出,这里的参数n如果不写,则默认按照计数由大到小输出所有的元素及其计数值。
也可以通过most_common()[:-n-1:-1]的方式来按照由小到大的顺序输出。
>>> c = Counter({'red': 2, 'green': 2, 'blue': 1})
>>> c.most_common()
[('green', 2), ('red', 2), ('blue', 1)]
>>> c.most_common(1)
[('green', 2)]
>>> c.most_common()[:-4:-1]
[('blue', 1), ('red', 2), ('green', 2)]

subtract([iterable-or-mapping]) 函数可以实现两个计数器的相减,当采用a.subtract(b)时,b没有改变,a的key和value都可能发生改变。该函数直接改变a,不会再返回一个Counter。
>>> a = Counter(x = 4, y = 2, z = 0)
>>> b = Counter(x = 1, y = 2, z = 3)
>>> a.subtract(b)
>>> a
Counter({'x': 3, 'y': 0, 'z': -3})

两个Counter之间直接求交并差:
>>> a = Counter(a = 3, b = 1)
>>> b = Counter(a = 1, b = 2)
>>> a + b
Counter({'a': 4, 'b': 3})
>>> a - b
Counter({'a': 2})
>>> a & b
Counter({'a': 1, 'b': 1})
>>> a | b
Counter({'a': 3, 'b': 2})

两个Counter直接相减与调用subtract函数的区别,前者会返回一个Counter,不会修改两个操作数,返回的结果不会增加key值,且不会出现负数(求差后计数为0或者负的时候返回的差集不包含该元素);而后者则返回None,直接修改第一个操作数的值,且对于0和负值都会保留在Counter里面。
>>> a = Counter({'x':1, 'z':2})
>>> b = Counter({'x':1, 'y':2})
>>> a - b
Counter({'z': 2})
>>> a.subtract(b)
>>> a
Counter({'z': 2, 'x': 0, 'y': -2})


OrderedDict defaultdict都比较直观,不再赘述。


参考:
http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001411031239400f7181f65f33a4623bc42276a605debf6000
https://docs.python.org/2/library/collections.html

你可能感兴趣的:([Python] collections — High-performance container datatypes)