前言
Python 内置模块collections, 目标是提供各种专门的集合数据类型来解决特定的编程问题。
本系列介绍其中的数据结构特点和使用方法, 以便遇到某些特定的问题,可以找到对应的数据来处理, 达到事半功倍的效果
⭐namedtuple | 用于创建其子类的工厂函数tuple ,提供命名字段,允许按名称访问项目,同时保持按索引访问项目的能力 |
⭐deque | 一个类似序列的集合,支持从序列的任一端有效地添加和删除项目 |
OrderedDict | 根据插入键的时间保持键值对排序的字典子类 |
ChainMap | 一个类似字典的类,允许将多个映射视为单个字典对象 |
defaultdict | 一个字典子类,用于为缺失的键构建默认值并自动将它们添加到字典中 |
Counter | 一个字典子类,支持方便地对序列或可迭代中的唯一项进行计数 |
本文介绍其中两个 namedtuple, deque, 其他的可以参考
Python中的collections模块(一) 使用Counter,pythonic的对象计数方式
Python中的collections模块(二) 有序字典OrderedDict和链接字典ChainMap
Python中的collections模块(四) UserString,UserList,和UserDict
1. 使用namedtuple提高代码可读性
tuple(元组)是python内置的数据结构,是一种不可变的有序数据集合.通过坐标索引来访问数据
In [1]: card = ('Spades', '7')
In [2]: card[0]
Out[2]: 'Spades'
使用namedtuple可以创建命名字段的tuple。可以使用点表示法和字段名称访问给定命名元组中的值,如obj.attr. 使用方式:namedtuple(typename:str, field_names:list or tuple)
In [3]: from collections import namedtuple
In [4]: Card = namedtuple('Cards', ['suit', 'rank'])
In [5]: spade_7 = Card('spades', '7')
In [6]: spade_7
Out[6]: Cards(suit='spades', rank='7')
In [7]: spade_7.suit
Out[7]: 'spades'
In [8]: spade_7.rank
Out[8]: '7'
# 同样支持坐标索引
In [9]: spade_7[0]
Out[9]: 'spades'
如以上例子所示,使用namedtuple创建的卡牌对象,可读性明显高于原始的tuple,通过属性和值,spade_7这张牌的花色是黑桃,值是7.
2. 高效插入和删除的双端队列-deque
使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。
deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈。deque除了支持和list同样功能的append和extend,还支持左端插入appendleft,extendleft。以及左侧弹出popleft
from collections import deque
In [1]: dq = deque([1,2,3])
In [2]: dq
Out[2]: deque([1, 2, 3])
In [3]: dq.extend([4,5,6])
In [4]: dq.appendleft(-1)
In [5]: dq
Out[5]: deque([-1, 1, 2, 3, 4, 5, 6])
In [6]: dq.popleft()
Out[6]: -1
2.1 maxlen和rotate
deque 可以指定maxlen来设置队列深度,超出深度的数据会被丢弃
In [7]: dq = deque([1,2,3,4,5,6],maxlen=4)
In [8]: dq
Out[8]: deque([3, 4, 5, 6])
In [9]: dq.append(7)
In [10]: dq
Out[10]: deque([4, 5, 6, 7])
In [11]: dq.appendleft(0)
In [12]: dq
Out[12]: deque([0, 4, 5, 6])
# 使用rotate,将数据向后滚动, n可以指定滚动的距离
In [13]: dq.rotate()
In [14]: dq
Out[14]: deque([5, 6, 0, 4])
In [15]: dq.rotate(2)
In [16]: dq
Out[16]: deque([4, 5, 6, 0])
2.2 使用deque实现栈和队列
class Stack:
def __init__(self, data, depth):
self.stack = deque(data, maxlen=depth)
def push(self, v):
self.stack.append(v)
def pop(self):
self.stack.pop()
class Queue:
def __init__(self, data, depth):
self.stack = deque(data, maxlen=depth)
def push(self, v):
self.stack.appendleft(v)
def pop(self):
self.stack.pop()
2.3 queue和list的性能对比
测试脚本
from time import perf_counter
from collections import deque
n = 100000
start = perf_counter()
lst = []
for i in range(10):
lst.extend(range(n))
now = perf_counter()
print(f'extend {i} :', now - start)
start = now
start = perf_counter()
dq = deque()
for i in range(10):
dq.extend(range(n))
now = perf_counter()
print(f'extend {i} :', now - start)
start = now
次数 | n=10万 | n=100万 | n=1000万 | |||
list | deque | list | deque | list | deque | |
1 | 0.00016 | 0.00024 | 0.01267 | 0.0199 | 0.135 | 0.18206 |
2 | 0.00099 | 0.00206 | 0.01868 | 0.01964 | 0.15235 | 0.18039 |
3 | 0.00043 | 0.00294 | 0.0208 | 0.0198 | 0.17042 | 0.18593 |
4 | 0.00040 | 0.00298 | 0.02512 | 0.01914 | 0.21075 | 0.19321 |
5 | 0.00037 | 0.0032 | 0.02268 | 0.01902 | 0.2191 | 0.18765 |
6 | 0.00030 | 0.00204 | 0.02456 | 0.01933 | 0.25137 | 0.21404 |
7 | 0.00048 | 0.00209 | 0.02464 | 0.01987 | 0.29451 | 0.18301 |
8 | 0.00046 | 0.00245 | 0.02577 | 0.02112 | 0.26184 | 0.17619 |
9 | 0.00033 | 0.00231 | 0.01328 | 0.01997 | 0.12787 | 0.179 |
10 | 0.00058 | 0.00283 | 0.03114 | 0.0204 | 0.32956 | 0.18637 |
从图中可以看出,随着数据的增大,list的extend的时间逐渐增大, deque的extend的时间相对平稳。当数据达到1000万级时,deque的性能优势才明显强于list。日常工作中少量的数据时,二者性能差距不大。
3.总结
① namedtuple 可以像访问对象的属性一样获取元组中的数据,提高代码的可读性。
② deque 提供左端的插入和弹出,100万级别的数据对比list的性能提升并不明显.