目录

2.4 Python入门之collections模块

2.4.1 命名元组 (namedtuple)

2.4.2 计数器

2.4.3 双向队列

2.4.4 有序字典(OrderedDict)

2.4.5 默认字典(defaultdict)


2.4 Python入门之collections模块

Python 除了基本的 list ,dict ,tuple ,set 等数据结构外,还提供了一个强大模块: collections 。模块提供了许多有用的集合类,灵活使用它们能提高我们使用 Python 编程的效率。

2.4.1 命名元组 (namedtuple)

Python 中提供了基础的不可变数据结构元组tuple,对元组元素的访问需通过索引来完成,对此需要熟记每个下标对应的具体含义。如果元素数量一多,要记清楚这些东西就会比较麻烦了,于是就出现了命名元组namedtuple

创建命名元组

命名元组的构造函数接受两个参数typenamefield_names

  • typename:元组的名字;
  • field_names:元组各个元素的名称,也就是属性名称。

比如:

  1. collections.namedtuple("Point",["x","y"])

这样就创建了一个叫做Point的命名元组,它拥有两个属性xy

第二个参数["x","y"]也可以写成"x y"或者"x,y",即用空格或者逗号隔开属性名,即:

  1. collections.namedtuple("Point","x y")
  2. collections.namedtuple("Point","x,y")

我们可以将其赋值给一个变量:

  1. Point = collections.namedtuple("Point","x,y")
  2. p = collections.namedtuple("Point","x,y") #变量名不一定要和第一个参数相同

以上得到的变量Point或者p并不直接是一个元组对象,它只是一个,如果要创建它的实例,则需要像创建类实例一样调用它:

  1. p1 = Point(x = 0, y = 0)
  2. p2 = p(x = 1, y = 1)

这样就创建了两个实例p1p2,他们的内容分别是x = 0,y = 0x = 1,y = 1

访问命名元组的元素

通过collections.namedtuple创建的命名元组类,实际上是元组类的子类,因此命名元组也可以通过索引访问元素:

  1. print(p1[0])
  2. print(p1[1])

得到的结果: 0 0

当然,命名元组也可以通过属性访问:

  1. print(p2.x)
  2. print(p2.y)

得到的结果: 1 1

修改元素

如果需要修改元组的元素,则不能简单的使用p1.x = 1,需要调用成员函数_replace(),它会返回一个包含新值的新实例,比如:

  1. p1 = p1._replace(x = 1) #将p1的x值从0换到1

2.4.2 计数器

计数器是一个无序容器,用于记录各种值出现的次数。它采用键值对的形式存储,要记录的值作为key,这个值出现的次数作为valuevalue值可正可负。

创建计数器

要创建一个计数器实例,可以调用它的无参构造函数:

  1. c = collections.Counter()

这样就创建了一个空的计数器实例c。

也可以从listtupledict,字符串等**可迭代对象(iterable)**创建:

  1. c = collections.Counter(['a','a','b','b','c']) #从list创建
  2. c = collections.Counter(('a','a','b','b','c')) #从tuple创建
  3. c = collections.Counter({'a':2,'b':2,'c':1}) #从dict创建
  4. c = collections.Counter("aabbc") #从字符串创建

最后,你也可以直接指定键值对,来创建计数器:

c = collections.Counter(a=2,b=2,c=1)

创建出来的计数器c,与上面四条语句创建的计数器c是相同的。

访问元素

计数器是dict子类,因此可以像使用dict那样访问计数器元素:

  1. print(c['a'])
  2. print(c['b'])
  3. print(c.c)

得到的结果是: 2 2 1

不过与dict不同的是,当访问计数器中不存在的元素的时候,不会产生异常,而是返回0,比如:

  1. print(c['d']) #c中没有d元素,但不会发生异常

上面的代码能够正常运行,并且结果是:0

增加计数与减少计数

要改变计数器中某一元素的值,除了可以使用操作dict的方式:c.a = XXX外,计数器还提供了两个成员函数updatesubt\fract

update函数接受一个可迭代对象,然后将这个对象中各种值出现的次数到计数器中,比如:

  1. c.update("aabbcd") #字符串也是可迭代对象

上面这行语句执行后,c的值从原来的: {'a': 2, 'b': 2, 'c': 1}

增加到了: {'a': 4, 'b': 4, 'c': 2, 'd': 1}

subtract函数与update函数类似,不过它是从计数器中减去可迭代对象中各种值出现的次数,比如:

  1. c.subtract("aabbcd")

上面这行语句执行后,c的值从原来的: {'a': 4, 'b': 4, 'c': 2, 'd': 1}

减少到了: {'a': 2, 'b': 2, 'c': 1, 'd': 0}

删除元素

从上面的例子可以发现,当计数器中一个元素的值减少到0时,它并不会自动从计数器中删除,如果要删除元素,可以使用del函数:

  1. del(c['d'])

上面这行语句执行后,c的值从原来的: {'a': 2, 'b': 2, 'c': 1, 'd': 0} 变成了: {'a': 2, 'b': 2, 'c': 1} ,即元素d被删除了。

TopN操作

计数器还提供了一个获取出现次数最多的n个元素的成员函数most_common,它返回包含这些元素的list,比如:

  1. top1 = c.most_common(1) #出现次数最多的元素
  2. print(top1)
  3. top2 = c.most_common(2) #出现次数最多的两个元素
  4. print(top2)
  5. all = c.most_common() #不提供参数则返回所有元素
  6. print(all)

得到的结果是: [('a', 2)] [('a', 2), ('b', 2)] [('a', 2), ('b', 2), ('c', 1)]

注意:如果有多个元素的值相同,那么它们之间的顺序是不可确定的,不要在它们的顺序上作任何假设。

2.4.3 双向队列

双向队列是一种能在队列两端都进行入队、出队操作的数据结构,比普通的队列更加灵活也更加复杂。

创建双向队列

就像计数器Counter,双向队列可以调用无参构造函数创建一个空队列,也可以使用可迭代对象创建,并初始化一个队列,比如:

  1. d = collections.deque() #创建一个空队列
  2. d = collections.deque(['a','b','c']) #从list创建
  3. d = collections.deque(('a','b','c')) #从tuple创建
  4. d = collections.deque({'a':0,'b':1,'c':2}) #从dict创建
  5. d = collections.deque("abc") #从字符串创建

第一行语句创建一个空队列,下面四行语句创建了含有元素abc的队列,要注意当从dict创建时,使用的是它的键key,而不是值value

队列操作

双向队列与list类似,也有appendpop这两个成员函数,他们的作用分别是向队列的右边增加元素和从队列的右边删除并返回一个元素,比如:

  1. d.append('d') #向右边增加一个元素'd'
  2. print(d)
  3. d.pop() #从右边删除一个元素
  4. print(d)

得到的结果: deque(['a', 'b', 'c', 'd']) deque(['a', 'b', 'c'])

appendpop相对应的,还有一组对队列左边进行操作的函数:appendleftpopleft,用法也与前面的一组类似:

  1. d.appendleft('+') #向左边增加一个元素'+'
  2. print(d)
  3. d.popleft() #从左边删除一个元素
  4. print(d)

得到的结果: deque(['+', 'a', 'b', 'c']) deque(['a', 'b', 'c'])

双向队列还提供了一对操作:extendextendleft,用于将一个可迭代对象的所有迭代值,依次加入到队列的右边或者左边:

  1. d1 = collections.deque()
  2. d1.extend("123")
  3. print(d1)

  4. d1 = collections.deque()
  5. d1.extendleft("123")
  6. print(d1)

得到的结果是: deque(['1', '2', '3']) deque(['3', '2', '1']) 可以注意到,上下两个结果的值的顺序是相反的

2.4.4 有序字典(OrderedDict)

有序字典和普通的dict基本上是相似的,只有一点不同,那就是有序字典中键值对的顺序会保留插入时的顺序

有序字典的创建方法和普通的dict类似,不过由于多了保留顺序的功能,因此在使用可迭代对象创建有序字典时,可以对它先排个序,让创建出来的字典元素也是有序的:

  1. data = [('a',1),('b',3),('c',2)]
  2. od = collections.OrderedDict(sorted(data,key=lambda s:s[0]))#按数据中key值的大小排序
  3. print(od)

  4. od = collections.OrderedDict(sorted(data,key=lambda s:s[1]))#按数据中value值的大小排序
  5. print(od)

得到的结果:

OrderedDict([('a', 1), ('b', 3), ('c', 2)])

OrderedDict([('a', 1), ('c', 2), ('b', 3)])

这里使用的sorted函数,它返回对一个可迭代对象排序后的结果,如果可迭代对象的元素不能直接进行比较(比如元素是一个listtuple等),则需要指定key函数。

这里使用lambda表达式lambda s:s[0]lambda s:s[1],分别指定keydata中每个元素(tuple类型)的第一个元素和第二个元素。

修改顺序

有序字典提供了一个move_to_end函数,这个函数可以将指定的键值对移动到最前面或者最后面,即最左边或最右边:

  1. dt = collections.OrderedDict()
  2. dt['a'] = 0
  3. dt['b'] = 1
  4. dt['c'] = 2

  5. dt.move_to_end('b',last = False) #将`b`键值对移动到最前方
  6. print(dt)
  7. dt.move_to_end('b',last = True) #将`b`键值对移动到最后方
  8. print(dt)

得到的结果:

OrderedDict([('b', 1), ('a', 0), ('c', 2)])

OrderedDict([('a', 0), ('c', 2), ('b', 1)])

2.4.5 默认字典(defaultdict)

默认字典的功能与dict基本相同,但在访问一个不存在的key时,默认字典会提供一个默认值,而不是引发异常。

创建默认字典

默认字典的构造函数接受一个工厂函数default_factory作为参数,可以将一个类型名看做是一个工厂函数,比如listtuplestr等。 这个函数会在要生成默认值的时候无参调用,如果使用类型名作为工厂函数,则这个类型必须要有无参构造函数,比如:

  1. dd = collections.defaultdict(int) #使用int作为工厂函数
  2. print(dd['a']) #访问不存在的key:'a'

  3. dd = collections.defaultdict(tuple) #使用tuple作为工厂函数
  4. print(dd['a']) #访问不存在的key:'a'

  5. dd = collections.defaultdict(str) #使用str作为工厂函数
  6. print(dd['a']) #访问不存在的key:'a'


  7. class Test:
  8. def __init__(self,name): #只有一个构造函数,而且它有一个参数
  9. print("init")

  10. dd = collections.defaultdict(Test) #使用自定义类型Test作为工厂函数
  11. print(dd['a']) #运行到这里就会出现异常,原因是Test类没有无参的构造函数

直到最后一行语句之前,上面的结果是:

0

()

第三行是字符串的默认值:空字符串。

如果不提供工厂函数,那么默认值的功能就失效了,此时默认字典与普通dict表现的功能一致:

  1. dd = collections.defaultdict()
  2. print(dd['a']) #虽然dd是一个默认字典,但由于没有指定工厂函数,没办法产生一个默认值,因此还是会发生异常