Python中的常用模块-collections模块

Python引入了collections模块以增强内置集合容器的功能。 Python collections模块在其2.4版本中首次引入。

在本文将讨论Python collections模块中最常用的3种数据结构。 它们是:

  • namedtuple命名元组
  • Counter
  • deque

Counter

Counterdict的子类,用于对集合中的元素计数。 其中元素存储为字典键,它们的计数存储为字典值。 计数可以是任何整数值,包括零或负数。

以下示例,都假设我们已经事先全量导入了collections模块。

>>> from collections import *

初始化Counter

有多种方法可以创建Counter对象。

  1. 最简单的方法是使用不带任何参数的 Counter()函数。

     >>> c = Counter() 
     >>> c
     Counter()
    
  2. 传递一个可迭代对象到Counter()函数中,创建一个Counter对象。

     >>> c1 = Counter('gallahad')
     >>> c1
     Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1})
     >>> c2 = Counter(['a','b','','a','c'])
     >>> c2
     Counter({'a': 2, 'b': 1, '': 1, 'c': 1})
     >>> c3 = Counter(('a','b','','a','c'))
     >>> c3
     Counter({'a': 2, 'b': 1, '': 1, 'c': 1})
    
  3. Counter()函数可以将字典作为参数。 在这个字典中,一个键的值应该是那个键的“计数”。

     >>> c = Counter({'red': 4, 'blue': 2})
     >>> c
     Counter({'red': 4, 'blue': 2})
    
  4. 最后,Counter()函数还可以使用关键字参数。

     >>> c = Counter(cats=4, dogs=8)
     >>> c
     Counter({'dogs': 8, 'cats': 4})
    

Counter的性质

Counterdict的特性相同,除了访问不存在的键时,dict引发KeyError错误,而Counter返回0

>>> c = Counter(['eggs', 'pork'])
>>> c['beef']
0

将计数设置为零不会从计数器中删除元素。 使用del将其删除:

>>> c = Counter(['eggs', 'pork'])
>>> c["eggs"]=0
>>> c
Counter({'pork': 1, 'eggs': 0})
>>> del c["eggs"]
>>> c
Counter({'pork': 1})

除所有字典都支持的方法之外Counter对象还支持以下几种方法:

  1. elements(),返回一个迭代器,每个元素的重复次数与其计数相同,元素按第一次遇到的顺序返回。如果元素的计数小于1,则elements()将忽略它。

     >>> c = Counter("internationalization")
     >>> print([x for x in c.elements()])
     ['i', 'i', 'i', 'i', 'n', 'n', 'n', 'n', 't', 't', 't', 'e', 'r', 'a', 'a', 'a', 'o', 'o', 'l', 'z']
    
  2. most_common([n])返回一个包含n个出现次数最多的元素及其从最多到最少排序的列表。 如果省略nNone,则 most_common()返回计数器中的所有元素。 具有相等计数的元素按第一次遇到的顺序排序:

     >>> c = Counter("internationalization")
     >>> c.most_common(3)
     [('i', 4), ('n', 4), ('t', 3)]
    
  3. total() 计算各元素计数的总和。

     >>> c = Counter(a=1, b=3, c=4, d=4)
     >>> c.total()
     12
    

Counter对象的比较和加减更新

计数器支持丰富的相等、子集和超集关系的比较运算符:==!=<<=>>=。 所有这些测试都将丢失的元素视为零计数,因此 `返回true`。

>>> Counter(a=1)==Counter(a=1,b=0)
True

提供了几种数学运算来组合Counter对象以生成新的计数器。 加法和减法通过增加或减去相应元素的计数来组合计数器。 交集和并集返回对应计数的最小值和最大值。 每个操作都可以接受带符号计数的输入,但输出将排除计数为零或更少的结果。

>>> c = Counter(a=3, b=1)
>>> d = Counter(a=1, b=2)
>>> c + d                   # 加法     
Counter({'a': 4, 'b': 3})
c - d                       # 减法
Counter({'a': 2})
c & d                       # 交集
Counter({'a': 1, 'b': 1})
c | d                       # 并集
Counter({'a': 3, 'b': 2})

一元操作符+-会作用到计数器上的每一项的符号上,同时它也是删除空计数器的快捷方式。

c = Counter(a=2, b=-4)
+c
Counter({'a': 2})
-c
Counter({'b': 4})

Counter上还有两个更新元素的方法:

  1. subtract([d]),从Counter中减去元素。d可以是可迭代对象或另一个dict(或Counter)。有点像dict.update(),但减去计数而不是替换它们。 输入和输出的Value都可以为零或负。

     >>> c1 = Counter(a=6, b=3, c=0)
     >>> c2 = Counter(a=1, b=3, c=4, d=4)
     >>> c1.subtract(c2)
     >>> c1
     Counter({'a': 5, 'b': 0, 'c': -4, 'd': -4})
    
  2. update([d]) 将元素加上字典或计数器的计数。

     >>> c= Counter(a=1, b=2, c=3)
     >>> c.update({"a":2})
     >>> c
     Counter({'a': 3, 'c': 3, 'b': 2})
    

namedtuple命名元组

命名元组为元组中的每个位置分配含义,使用其更具可读性、代码自文档化。 它们可以在使用常规元组的任何地方使用,并且它们增加了按名称而不是位置索引访问字段的能力。

创建命名元组类

namedtuple(typename, field_names, *, rename=False, defaults=None, module=None),返回一个名为typename的新元组子类。新的子类用于创建类似元组的对象,这些对象具有可通过属性访问字段,并且也保留了无组可索引和可​​迭代的特性。

field_names参数指定了命名元组的字段,同时它也起到了文档字符串的作用,列出命令元组子类实例的时候以name=value的形式。

>>> Student = namedtuple("Student", ["id", "name"])
>>> s = Student("09", "xiaoMing")
>>> s
Student(id='09', name='xiaoMing')

field_names可以是单个字符串,每个字段名由空格、/或逗号分隔,例如id name'id, name'

命名元组支持按索引和字段名访问:

>>> s[0]
'09'
>>> s[1]
'xiaoMing'
>>> s.id
'09'
>>> s.name
'xiaoMing'

还支持解包:

>>> id, name = s
>>> id, name
('09', 'xiaoMing')

命名元组支持的函数

  1. _make(iterable),从可迭代参数iterable创建命名元组的新实例。

     >>> s1 = ["08", "xiaoHong"]
     >>> Student._make(s1)
     Student(id='08', name='xiaoHong')
    

    对于从csvsqlite3模块返回的结果,使用_make方法将它们分配给命令数组非常有用。

     Student = namedtuple('Student', 'id, name, age, className, score')
    
     import csv
     for emp in map(Student._make, csv.reader(open("students.csv", "rb"))):
         print(emp.name, emp.age)
    
     import sqlite3
     conn = sqlite3.connect('/studentdata')
     cursor = conn.cursor()
     cursor.execute('SELECT id, name, age, className, score FROM students')
     for emp in map(Student._make, cursor.fetchall()):
         print(emp.name, emp.age)
    

deque两端队列

双端队列是栈和队列的泛化(名称发音为“deck”,是“双端队列”的缩写)。 双端队列支持线程安全、内存高效的追加和从双端队列的任一侧弹出,在任一方向上的性能大致能达到O(1)

创建deque

deque位于collections模块下,使用前应先导入collections模块:from collections import deque

使用如下的语法创建deque对象:

deque([iterable[, maxlen]])

返回一个从左到右初始化的新deque对象(使用 append()),其中包含来自iterable的数据。 如果未指定iterable,则新的双端队列为空。

如果未指定maxlen或指定None,则双端队列可能会增长到任意长度。 否则,双端队列受限于指定的最大长度。 一旦有界长度的双端队列已满,当添加新项目时,从对端丢弃相应数量的项目。它们还可用于跟踪仅对最近的活动感兴趣的交易和其他数据池。

deque的方法

  • append(x):将x添加到双端队列的右侧。
  • appendleft(x):将x添加到双端队列的左侧。
  • clear():从双端队列中删除所有元素,使其长度为 0。
  • copy():创建双端队列的浅拷贝。
  • count(x):计算等于x的双端队列元素的数量。
  • extend(iterable):通过添加来自可迭代参数的元素来扩展双端队列的右侧。
  • extendleft(iterable):通过添加可迭代元素来扩展双端队列的左侧。 请注意,一系列左追加会导致可迭代参数中元素的顺序颠倒。
  • index(x[, start[, stop]]):返回x在双端队列中的位置(在索引开始时或之后和索引停止之前)。返回第一个匹配项,如果未找到,则引发ValueError
  • insert(i, x):将x插入到双端队列中位置i处。如果插入会导致有界双端队列增长超过maxlen,则会引发IndexError
  • pop():从双端队列的右侧移除并返回一个元素。 如果不存在元素,则引发IndexError
  • popleft():从双端队列的左侧移除并返回一个元素。 如果不存在元素,则引发IndexError
  • remove(value):删除第一次出现的值。 如果未找到,则引发ValueError
  • reverse():就地反转双端队列的元素,然后返回None
  • rotate(n=1):向右旋转dequen步。 如果n为负,则向左旋转。 当双端队列不为空时,向右旋转一级相当于d.appendleft(d.pop()),向左旋转一级相当于d.append(d.popleft())

Deque对象还提供了一个只读属性:

  • maxlen: 双端队列的最大大小或None(如果无界)。
>>> d = deque('ghi')                 # make a new deque with three items
>>> for elem in d:                   # iterate over the deque's elements
...     print(elem.upper())
G
H
I

>>> d.append('j')                    # add a new entry to the right side
>>> d.appendleft('f')                # add a new entry to the left side
>>> d                                # show the representation of the deque
deque(['f', 'g', 'h', 'i', 'j'])

>>> d.pop()                          # return and remove the rightmost item
'j'
>>> d.popleft()                      # return and remove the leftmost item
'f'
>>> list(d)                          # list the contents of the deque
['g', 'h', 'i']
>>> d[0]                             # peek at leftmost item
'g'
>>> d[-1]                            # peek at rightmost item
'i'

>>> list(reversed(d))                # list the contents of a deque in reverse
['i', 'h', 'g']
>>> 'h' in d                         # search the deque
True
>>> d.extend('jkl')                  # add multiple elements at once
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])
>>> d.rotate(1)                      # right rotation
>>> d
deque(['l', 'g', 'h', 'i', 'j', 'k'])
>>> d.rotate(-1)                     # left rotation
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])

>>> deque(reversed(d))               # make a new deque in reverse order
deque(['l', 'k', 'j', 'i', 'h', 'g'])
>>> d.clear()                        # empty the deque
>>> d.pop()                          # cannot pop from an empty deque
Traceback (most recent call last):
    File "<pyshell#6>", line 1, in -toplevel-
        d.pop()
IndexError: pop from an empty deque

>>> d.extendleft('abc')              # extendleft() reverses the input order
>>> d
deque(['c', 'b', 'a'])