这篇文章上次修改于 1130 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

python - 选择更高效的容器

整理自fluent-python “当列表不是首选时”

注:此文暂不包括字典类对象

python的常用容器

《fluent-python》一书把python容器以两种方式区别,分别为元素是否可以不同类容器是否不可变

python 常用的容器有 list,tuple,collection.deque,str, bytes,bytearray,memoryview,array.array等等

容器类型 元素可以不同类 可变类型
list
tuple
collection.deque
str
bytes
bytearray
memoryview
array.array

list就不过多赘述,已经是非常常见的类型了

array — 高效的数值数组

官方文档

简介

array类似于C 语言数组,相较于list有着快速的处理速度和较小的内存占用。如果需要储存的数据都属于数字类型,array是一个不错的选择,正如上面所述,array属于可变类型但它只能包含一定范围内数字的元素。

创建array需要指定类型码

类型码

已定义的类型码如下:

类型码 C 类型 Python 类型 以字节表示的最小尺寸 备注
'b' signed char int 1 (-128~127)
'B' unsigned char int 1 (0~255)
'u' wchar_t Unicode 字符 2 (1)
'h' signed short int 2 (-32767 ~ 32768)
'H' unsigned short int 2 (0 ~ 65536)
'i' signed int int 2 (-231 ~ 231 - 1)
'I' unsigned int int 2 (0 ~ 232 - 1)
'l' signed long int 4 (- 263~263 - 1)
'L' unsigned long int 4 (0~264 - 1)
'q' signed long long int 8 (-2127~2127 - 1)
'Q' unsigned long long int 8 (0~2128 - 1)
'f' float float 4 (-3.4E+38~3.4E+38)
'd' double float 8 (-1.7E-308~1.7E+308)

注释:

  1. 由于平台的不同它可能为 16 位或 32 位。

    在 3.9 版更改: array('u') 现在使用 wchar_t 作为 C 类型而不再是已弃用的 Py_UNICODE。 这个改变不会影响其行为,因为 Py_UNICODE 自 Python 3.3 起就是 wchar_t 的别名。

    Deprecated since version 3.3, will be removed in version 4.0.

创建的方法是

# arr_1 = array.array(typecode[, initializer])
floats = array('d', (random() for i in range(10**7)))  # 例:利用推导式

在创建出数组对象后可以访问其typecode属性获取类型码

和列表的区别

1.不支持.clear()函数清空

如果是list等对象,可以用过list1.clear()将列表置空,在array类型会提示无该对象

2.不支持.copy()的浅复制

虽然不支持.copy()但是支持 copy包的copy和deepcopy

3.不支持.sort()的数据整理

可以使用sort函数对象例如

a = array.array(a.typecode, sorted(a))

deque — 双向队列

官方文档

简介

双向队列正如其名,该容器可以在两端进行非常快速的操作,如果向注重时效性,deque也是个不错的选择。

deque更多应用于多线程操作,它是一个线程安全的容器,即多数操作为原子操作,在多线程中不会出现多个线程同时操作一个资源造成的惨案。

插播:原子操作

由于多线程可能同时操作一个资源,而导致不好的结果,于是提出了线程锁,线程可以在操作资源的时候打开锁而在操作完资源后关闭锁来避免悲剧的发生。原子操作是指不会被线程调度机制打断的操作,线程运行不可能同时在该操作上导致冲突,总的来说就是在多线程对资源使用原子操作可以避免问题,并且不需要引入线程锁。

python官方文档指出

以下操作为原子操作

L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()

这些不是:

i = i+1
L.append(L[-1])
L[i] = L[j]
D[x] = D[x] + 1

和列表的区别

1.maxlen最大长度

deque相比list,在创建的时候可以添加最大长度的参数,当队列达到最大长度后,新添加元素的操作将会从另一端删去元素。

2.retate()函数操作

deque还支持向左或向右移动元素,最左或最右的元素被移出后将被补充到另一端。例如:

a = deque((1, 2, 3, 4))  # deque([1, 2, 3, 4])
a.rotate(1)  # deque([4, 1, 2, 3])

当然移动的参数可以为负数,即为反方向移动。

3.多数操作为原子操作

deque对于多线程十分友好,append和pop都为原子操作。

4.双向特性

deque支持双向操作例如append()和appendleft(),pop()和popleft(),a.extend()和a.extendleft()

但要注意的是.extendleft()是逐个加入,例如:

a = deque((1, 2, 3, 4))
a.extendleft((4, 5, 6, 7))  # deque([7, 6, 5, 4, 1, 2, 3, 4])

5.不支持.copy()的浅复制

虽然不支持.copy()但是支持 copy包的copy和deepcopy

6.不支持切片

要实现 deque切片, 使用一个类似的方法,应用rotate()将目标元素放到左边。通过 popleft() 移去老的条目(entries),通过 extend()添加新的条目, 然后反向 rotate。

7.不支持.sort()的数据整理

同理支持sort函数

memoryview — 内存视图

官方文档

简介

内存视图是对bytes-like对象使用的,可以对其进行字节处理。它的特性在于不会拷贝内容,而是创建一个类似指针功能的对象。可以把memoryview 理解为一个放大镜或是一种工具,它可以处理bytes类型对象,例如open函数打开文件读取字节。

特性

和列表性质都不一样,此处不做比较

1.切片创建视图而非复制

若a = list1[1:2]那么a显然是list1切片出来的对象的副本,即内存进行了复制。memoryview 也支持切片,但其切片创建的对象不会进行复制。

2.可操作数组对象

numbers = array.array('h', [-2, -1, 0, 1, 2])
memv = memoryview(numbers)
memv_oct = memv.cast('B')
memv_oct.tolist()  # [254, 255, 255, 255, 0, 0, 1, 0, 2, 0]