Python集合模块 Collections

概述

在collections中,主要包括如下相关类和方法:

相关类/方法 描述
namedtuple() 创建命名元组子类的工厂函数,生成可以使用名字来访问元素内容的tuple子类
deque 类似列表(list)的容器,实现了在两端快速添加(append)和弹出(pop)
ChainMap 类似字典(dict)的容器类,将多个映射集合到一个视图里面
Counter 字典的子类,提供了可哈希对象的计数功能
OrderedDict 字典的子类,保存了他们被添加的顺序,有序字典
defaultdict 字典的子类,提供了一个工厂函数,为字典查询提供一个默认值
UserDict 封装了字典对象,简化了字典子类化
UserList 封装了列表对象,简化了列表子类化
UserString 封装了字符串对象,简化了字符串子类化

我们可以引入该模块,然后打印出对应的信息:

1
2
3
4
import collections
print(collections.__all__)

['ChainMap', 'Counter', 'OrderedDict', 'UserDict', 'UserList', 'UserString', 'defaultdict', 'deque', 'namedtuple']

Named Tuple

named tuple含义为命名元组,它可以帮助我们生成一个允许使用名字访问元素内容的Tuple子类,提高程序的可读性。

例如,如果我们需要表示平面上的一个点,我们可以使用一个元组(x, y)来进行表示,但是这样的元组只能用下标来访问其中的内容。另一种解决方案是定义一个类Point,其中保存对应的坐标。而命名元组实际上就可以帮助我们更加简单地构造出这样一个类,利用该方法,我们可以指定类名以及元组的名次,之后就可以用更加具有可读性的代码来完成相关工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])

point = Point(1, 2)
# 使用字段名
print(point.x) # 1
print(point.y) # 2
# 使用index
print(point[0]) # 1
print(point[1]) # 2
# 实际上就是构造出的类 Point 就是内置 tuple 的一个子类
print(isinstance(point, Point)) # True
print(isinstance(point, tuple)) # True

namedtuple可以接收如下参数:

  • typename:指定所创建的tuple子类的类名
  • field_names:接收一个字符串序列,用于表示tuple中的字段名称
  • rename:如果该参数设置为True,那么无效的字段会被自动替换为位置名称
  • verbose:如果该参数设置为True,那么子类被创建之后,类定义就会被立即打印出来
  • module:用于指定类所在的module

Deque

python原生的list类型是线性存储数据的,按照索引访问元素很快,但是数据量很大的时候,插入和删除效率较低,而deque是一个双端队列,其中高效实现了插入和删除操作,适合用于队列和栈。

1
2
3
4
5
6
from collections import deque

q = deque(['a', 1, '3'])
q.append(2)
q.appendleft('c')
print(q) # deque(['c', 'a', 1, '3', 2])

deque和list具有几乎相同的使用方式,相关方法如下:

  • append(x):向队尾添加元素
  • appendleft(x):向队首添加元素
  • clear():清空队列元素
  • copy():获得一个浅拷贝
  • count(x):计算队列中值为x的个数
  • extend(x_deque):向队尾扩展队列
  • extendleft(x_deque):向队首扩展队列
  • index(x):获取x的下标index
  • insert(index, x):在指定index处插入值x
  • pop():弹出队尾元素
  • popleft():弹出队首元素
  • remove(value):删除第一个value值
  • reverse():队列反转
  • rotate(n=1):将队列向右循环移动n步
  • maxlen:deque的最大尺寸

Chain Map

ChainMap可以把一组dict组合成一个逻辑上的dict。在使用的使用,仍然可以按照操作一个dict那样操作,但是内部的逻辑则会在这组dict上依次进行。实际上,ChainMap实际上就是一个映射链,在进行操作的时候,会依次在内部的链上进行。

ChainMap的一个常见应用场景是程序参数的获取。在获取应用程序参数的时候,我们通常可以从三个地方获取,分别是命令行传入、环境变量传入以及默认参数,它们可以对应三个dict,利用ChainMap可以实现参数的优先级查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from collections import ChainMap
import os
import argparse

# default args
defaults = {
'color': 'red',
'user': 'guest'
}

# cmd args
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
command_line_args = {k: v for k, v in vars(namespace).items() if v}

# 组合成ChainMap, 优先级依次下降:cmd args > env args > default args
combined = ChainMap(command_line_args, os.environ, defaults)

# 打印参数:
print('color=%s' % combined['color'])
print('user=%s' % combined['user'])

之后我们可以使用不同方式传入相关参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 不主动传入任何参数,则使用默认参数
python3 collections-test.py
color=red
user=guest

# 传入命令行参数,则使用命令行参数
python3 collections-test.py -u bob
color=red
user=bob

# 同时传入命令行参数和环境变量,命令行参数优先级更高
user=admin color=green python3 collections-test.py -u bob
color=green
user=bob

Counter

Counter是一个计数器,可以帮助我们统计次数。它本身也是dict的一个子类,key为元素值,value则为对应元素值出现的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from collections import Counter

# 像操作dict一样使用计数器
count = Counter()
for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
count[word] += 1
print(count)
# Counter({'blue': 3, 'red': 2, 'green': 1})

# 直接传入列表进行计算
print(Counter(['red', 'blue', 'red', 'green', 'blue', 'blue']))
# Counter({'blue': 3, 'red': 2, 'green': 1})

# 直接传入字符串进行计算
print(Counter("abcabcabcd"))
# Counter({'a': 3, 'b': 3, 'c': 3, 'd': 1})

Counter作为dict的子类,能够提供dict相关的方法,同时它本身还提供了一些特有的方法:

  • elements():返回一个迭代器,其中每个元素将重复计数器所指定的次数,同时元素会按照首次出现的顺序返回
  • most_common(n):返回一个列表,其中包含n个出现最多的元素以及对应的出现次数。计数值相等的元素按照首次出现的顺序进行排序。如果n为None,则返回所有元素
  • substract(another_counter):从迭代对象或者映射对象减去元素,对应值对应相减

此外,Counter还提供强大的数学操作功能,要点在于对应位置的值进行对应操作,如果得到的计数小于等于0则会被忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from collections import Counter

c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
# c[x] + d[x]
print(c + d) # Counter({'a': 4, 'b': 3})

# c[x] - d[x]
print(c - d) # Counter({'a': 2})

# intersection: min(c[x], d[x])
print(c & d) # Counter({'a': 1, 'b': 1})

# union: max(c[x], d[x])
print(c | d) # Counter({'a': 3, 'b': 2})

Ordered Dict

在使用dict的时候,key是无序的,因此我们在对dict进行迭代的时候,是无法确定key的顺序的,而OrderedDict就为我们解决了这个问题。它可以按照插入的顺序对Key进行保存,这样在进行迭代的时候同样是可以保证顺序的。

1
2
3
4
5
from collections import OrderedDict

orderedDict = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
print(orderedDict) # OrderedDict([('a', 1), ('b', 2), ('c', 3)])
print(orderedDict.keys()) # odict_keys(['a', 'b', 'c'])

OrderedDict的使用方法几乎等同于内置的dict,不过它自身还是提供了一些其他的功能:

  • popitem(last=True):移除字典中的一个键值对。如果last为真,则按照LIFO后进先出的顺序返回键值对,否则按照FIFO先进先出的顺序返回键值对
  • move_to_end(key, last=True):将key对应的键值对移动到其中一端。如果last为True则移动到最后,否则移动到开头
  • reversed(order_list):相对于通常的映射方法,有序字典还另外提供了逆序迭代的支持

需要注意,Python3.8之后,内置的dict已经是有序的了。

Default Dict

使用Python内置的dict,如果我们访问一个不存在的key,则会抛出KeyError错误。而defaultdict可以帮助我们指定默认值,如果Key不存在,返回的是实现指定的默认值。而default dict的其他使用方式与内置的dict完全相同。

defaultdict对象包含一个名为default_factory的属性。在构造的时候,第一个参数用于为该属性提供初始构造方法,默认为 None。所有其他参数(包括关键字参数)都相当于传递给 dict 的构造函数。

1
2
3
4
5
6
7
from collections import defaultdict

s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list)
for k, v in s:
d[k].append(v)
print(d) # defaultdict(<class 'list'>, {'yellow': [1, 3], 'blue': [2, 4], 'red': [1]})

例如这里我们使用list作为default_factory。当第一次遇见某个key的时候,它还没有在字典里面,所以会自动创建,调用default_factory方法返回一个空的list,之后空的list进行append。再次遇到对应的key,就与正常dict操作相同了。

基类

除了上面专门的数据类型之外,在Collections中还提供了三个基类,分别是UserDict、UserList和UserString,以方便用户创建自定义的字典、列表和字符串。如果我们想要自定义类似于dict、list和str的类,那么可以继承这些基类,之后进行个性化地定制即可。

参考文章

  1. Collections-Python Doc
  2. 万字长文详解 Python库collections,让你击败99%的Pythoner
  3. collections python

Python集合模块 Collections
http://example.com/2023/04/24/Python集合模块-Collections/
作者
EverNorif
发布于
2023年4月24日
许可协议