Python 标准库之 collections
0X00 Header
相信各位肯定都对 Python 中的基础、常见数据类型和数据结构比较熟悉了吧,不管是 int、float、string、bool 还是 list、tuple、set 用起来应该也都是手到擒来了吧。下面我们就来简单了解一下相对高级一些的 Python 内置数据结构,这些数据结构全都在 collections 的标准库中。
掌握这些数据结构虽然并不能让你「精通 Python」,但起码可以让你的代码更加 Pythonic,也能让你少写几行冗余的代码。
0X01 ChainMap
首先是 ChainMap,光是看名字大概都能猜到这东西的用途了:把 Map 组装为 Chain 嘛。在 Python 中最常见的也就是字典了,所以这个数据结构的主要功能就是将多个字典加在一起。
如果你手上有 100 个字典,现在你需要将他们加在一起,在没有 ChainMap 的时候大概率会写出这样的代码:先整一个 result 作为最终结果的容器,然后遍历这 100 个字典,一遍遍 update
1dict_list = [dict_0, dict_1, dict_2.......]
2
3result = dict()
4for d in dict_list:
5 result.update(d)
6
7print(result)现在有了 ChainMap 之后可以写成这样:将所有的 dict 都作为 ChainMap 参数传进取,它就自己帮你拼好了。
1from collections import ChainMap
2
3dict_list = [dict_0, dict_1, dict_2.......]
4result = ChainMap(*dict_list)有一点需要注意的是,使用第一段代码时由于是从前向后遍历
update所以是用后面的值覆盖前面的值;而使用ChainMap方式则不会覆盖,换句话说就是「先入为主」了可以用下面这段代码来测试这个说法
1from collections import ChainMap
2
3a = {'a': 1, 'b': 2, 'c': 3}
4b = {'a': 111, 'c': 333, 'd': 444}
5
6res = dict()
7res.update(a)
8res.update(b)
9print(res.get('a'))
10
11res = ChainMap(a, b)
12print(res.get('a'))0X02 Counter
类似这么个需求:有一个列表,里面全是单次,需要统计这些单次出现的次数。简单的写法基本上就是
1word_list = ['hello', 'world', .....]
2
3count = dict()
4for word in word_list:
5 if word in count:
6 count[word] += 1
7 else:
8 count[word] = 1
9
10print(count)但是这一大坨代码看起来就还是很麻烦,这时候如果有一个现成的 count 出现就好了。Counter 其实就是来解决这类问题的,我们来看一下用 Counter 解决这个问题是怎样的
1from collections import Counter
2
3word_list = ['a', 'b', 'c', 'c', 'd', 'e', 'a', 'a', 'a', 'a', 'a']
4
5count = Counter()
6for word in word_list:
7 count[word] += 1
8
9print(count)可见 Counter 类其实就是个字典的子类,每次 key 不存在的时候就当它是一个 0。改用了 Counter 之后数数的过程一下就从 5 行变为了 2 行。如果说只是节省这三两行代码,那确实没什么必要这么大动干戈。Counter 还为我们提供了 5 个非常常用的方法:
elements()返回迭代器,每个 key 会出现 value 次,当 value 小于 1 时忽略most_common(n)返回最常见的 n 个元素,也就是 value 最大的 n 个元素update(c)与dict的普通update不同,这里是计算加法,也就是c1.update(c2)意味着c1[x] + c2[x](相同 key 的 value 做加法)subtract(c)与上面的update类似,这里是减法total()算总量
这里还是给出这五个方法的例子
1from collections import Counter
2
3word_list = ['a', 'b', 'b', 'c', 'c', 'd', 'e', 'a', 'a', 'a', 'a', 'a']
4
5count = Counter()
6for word in word_list:
7 count[word] += 1
8
9print(count)
10# OUTPUT: Counter({'a': 6, 'b': 2, 'c': 2, 'd': 1, 'e': 1})
11
12print(list(count.elements()))
13# OUTPUT: ['a', 'a', 'a', 'a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'e']
14
15print(count.most_common(3))
16# OUTPUT: [('a', 6), ('b', 2), ('c', 2)]
17
18print(count.total())
19# OUTPUT: 12
20
21
22new_word_list = ['a', 'b', 'c', 'd', 'e']
23new_count = Counter()
24
25for word in new_word_list:
26 new_count[word] += 1
27
28count.update(new_count)
29print(count)
30# OUTPUT: Counter({'a': 7, 'b': 3, 'c': 3, 'd': 2, 'e': 2})
31
32count.subtract(new_count)
33print(count)
34# OUTPUT: Counter({'a': 6, 'b': 2, 'c': 2, 'd': 1, 'e': 1})0X03 deque
了解的同学肯定了解,栈和队列基本是继链表之后最简单的数据结构了。这个 deque 就是在队列的基础上有一个简单变种的版本:双向队列。队列本身规则很简单,就是单纯的先进先出(FIFO),基本操作也就只有两个:入队和出队。这双向队列就是将连个基本操作改成四个了:左右入队和左右出队。
当双向队列的长度没有收到限制时,两侧可以自由的入队出队;当长度受到限制时,队列爆满后继续入队就会导致另一侧强制出队。
Python 中的 deque 提供了下面这些方法:
append(x)从右侧入队appendleft(x)从左侧入队clear()清空队列copy()创建一份浅拷贝count(x)统计 x 出现了多少次extent(iterable)将可叠戴对象从右侧扩展进来extentleft(iterable)从左侧index(x)返回 x 在队列中的位置,找不到会 ValueErrorinsert(i, x)在队列中指定位置插入,队列爆满会 IndexErrorpop()从右侧出队popleft()从左侧出队remove(x)找到第一个 x 并删除,找不到会 ValueErrorreverse()反转队列rotate(n)向右循环 n 位,n 为负数时就是向左。向右循环的意思是:右侧出队一个元素,从左侧入队,也就是说d.appendleft(d.pop())
还提供了一个只读属性:maxlen,为 None 就是无限大,只有在 deque(maxlen=n) 初始化的时候可以指定
0X04 defaultdict
顾名思义:这是一个带有默认值的字典。我们可以在实例化它的时候指定默认值是什么,这里用它来搞一个非常简单的 Counter 吧
1from collections import defaultdict
2
3count = defaultdict(int)
4text = 'aaaaaaaabbbbbbbbbcccccccccc'
5for c in text:
6 count[c] += 1
7
8print(count)
9# OUTPUT: defaultdict(<class 'int'>, {'a': 8, 'b': 9, 'c': 10})这里可以看到我们在实例化的时候给他规定了一个类型 int,它的默认值就是 0 了,同样还可以用 dict、list、set 这些类型。
0X05 namedtuple
如果我们想存下一台电脑的配置,那么用普通的一堆变量可以、用 dict 可以,甚至用 class 也可以。但是有这么一个更好用且更合适的东西:namedtuple,中文叫命名元组或者具名元组。
1from collections import namedtuple
2
3Computer = namedtuple('Computer', ['CPU', 'GPU', 'Memory', 'Storage', 'OS'])
4
5macbook_14 = Computer('M2Max', 'M2Max', '32G', '1T', 'macOS')
6pc = Computer('i7-13900k', 'RTX 4090', '64G', '4T', 'Linux')
7another_pc = Computer(GPU='GT710', CPU='AMD A8', OS='Windows 7', Storage='500G', Memory='8G')
8
9print(macbook_14)
10print(pc.CPU, pc.GPU, pc.OS)
11print(another_pc)
12
13# OUTPUT:
14# Computer(CPU='M2Max', GPU='M2Max', Memory='32G', Storage='1T', OS='macOS')
15# i7-13900k RTX 4090 Linux
16# Computer(CPU='AMD A8', GPU='GT710', Memory='8G', Storage='500G', OS='Windows 7')0X06 OrderedDict
本来它最大的特性就是会保留插入顺序,但是自从 Python 3.7 的更新使普通的 dict 也是有序的之后,这个 OrderedDict 就变得略微有那么一点点尴尬了。
不过虽然普通的 dict 也开始有序了,但是 OrderedDict 也还是多少有一些不同的
- 常规的
dict擅长映射,排序是次要的 OrderedDict排序性能更好,但是空间效率和操作性能是相对更差的
如果这篇文章对你有帮助,可以请我喝杯咖啡 ☕
评论