一、数组
如果一个列表只包含数值,那么使用array.array会更加高效,数组不仅支持所有可变序列操作(.pop、.insert、.extent等),而且还支持快速加载项和保存项的方法(.fromfile、.tofile等)
创建array对象时要提供类型代码,它是一个字母,用来确定底层使用什么C类型存储数组中各项,并且指定类型后,不允许向数组中添加与指定类型不同的值。类型码如下所示:
Typecode | C Type | python Type | size in bytes |
'b' | signed char | int | 1 |
'B' | unsigned char | int | 1 |
'h' | signed short | int | 2 |
'H' | unsigned short | int | 2 |
'i' | signed int | int | 2 |
'I' | unsigned int | int | 2 |
'l' (lower L) | signed long | int | 4 |
'L' | unsigned long | int | 4 |
'q' | signed long long | int | 8 |
'Q' | unsigned long long | int | 8 |
'f' | float(单精度浮点数) | float | 4 |
'd' | double(双精度浮点数) | float | 8 |
示例:创建、保存、加载一个大型浮点数数组
from array import array
from random import random
# 创建一个双精度浮点数数组
floats = array('d', [random() for i in range(10**7)])
print(floats[-1]) # output: 0.6150799221528432
# 将数组写入文件
with open('floats.bin', 'wb') as fp:
floats.tofile(fp)
floats2 = array('d')
# 从二进制文件中读取1000万个数并赋值给float2
with open('floats.bin', 'rb') as fp:
floats2.fromfile(fp, 10**7)
print(floats2[-1]) # output: 0.6150799221528432
print(floats == floats2) # output: True
处理大型数值时使用数组的优势:
1.效率高:在使用array.tofile和array.fromfile时,发现二者的运行速度非常快,读取时无须使用内置函数float一行一行解析,比从文本文件中读取快;保存文件也比一行一行写入文本文件快很多。
2.占用内存少:保存1000万个双精度浮点数的二进制文件占80 000 000个字节(一个双精度浮点数占8字节);保存相同数据量文本文件占181 515 739字节。
补充:array类型没有列表那种就地排序算法sort,如果需要对数组进行排序,需要使用内置函数sorted重新构建数组
from array import array
num = array('i', sorted([12, 432, 5, 6]))
print(num) # output: array('i', [5, 6, 12, 432])
二、memoryview
内置的memoryview类是一种共享内存的序列类型,可以在不复制字节的情况下处理数组的切片,对处理大型数据集来说是非常重要的。
memoryview允许python代码访问支持缓冲协议的对象的内部数据,而无需复制(内存视图直接引用目标内存)。支持缓冲协议的对象:在python中有某些对象可以包装对底层内存阵列或缓冲区的访问,包括内置对象bytes和bytearray以及一些如array.array的扩展类型。
示例:展示如何将同一个6字节数组处理为不同的视图
from array import array
octets = array('B', range(6))
m1 = memoryview(octets)
# .tolist()表示将缓存区内的数据以一个列表的形式返回
print(m1.tolist()) # output: [0, 1, 2, 3, 4, 5]
# 根据前一个memoryview对象构建一个新的memoryvie对象,并转换为2行3列
m2 = m1.cast('B', [2, 3])
print(m2.tolist()) # output: [[0, 1, 2], [3, 4, 5]]
# 转换为3行2列
m3 = m1.cast('B', [3, 2])
print(m3.tolist()) # output: [[0, 1], [2, 3], [4, 5]]
m2[1, 1] = 22
m3[1, 1] = 33
# 显示原数组,证明octets、m1、m2、m3之间的内存是共享的
print(octets) # output: array('B', [0, 1, 2, 33, 22, 5])
三、NumPy
如果想对数组做一些高级数值处理应该使用NumPy库。NumPy实现了多维同构数组和矩阵类型,处理存放数值之外,还可以存放用户定义的记录,而且提供了高效的元素层面操作。
NumPy的数组类被调用N维数组对象ndarray,它是一系列同类型数据的集合,这与python标准库类array中的array不同,array.array只处理一维数组并提供较少的功能。
ndarray对象的重要属性:
ndarray.ndim:数组的轴(维度)的个数;
ndarray.shape:返回一个整数的元组,表示每个维度中数组的大小,shape元组的长度就是维度可数ndim;
ndarray.size:数组元素的总数,等于 shape 各个元素的乘积;
ndarray.dtype:一个描述数组中元素类型的对象;
ndarray.itemsize:数组中每个元素的字节大小。
示例:numpy.ndarray中行和列的基本操作
import numpy as np
# arange([start, ]stop[, step]用于生成数组ndarray,值在半开区间[start,stop]内
a = np.arange(12)
print(a) # output: [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(type(a)) # output: <class 'numpy.ndarray'>
print(a.size) # output: 12
print(a.shape) # output: (12,)
print(a.dtype) # output: int64
print(a.itemsize) # output: 8
# 改变数组的维度
a.shape = 3, 4
print(a)
# output:
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a.ndim) # output: 2
print(a[:, 1]) # output: [1 5 9]
print(a[2, 1]) # output: 9
# 转置数组
print(a.transpose())
# output:
# [[ 0 4 8]
# [ 1 5 9]
# [ 2 6 10]
# [ 3 7 11]]
NumPy还支持一些高级操作,如加载、保存和操作numpy.ndarray对象的所有元素
numpy.savetxt():以简单的文本文件格式(txt文件或csv文件)存储数据;
numpy.loadtxt():基本功能是从文本文件中读取数据,并将其转换为NumPy数组,主要处理如CSV文件或空格分隔的文件,它会自动处理数据的分隔符、数据类型和行结束符,使读取文本数据变得简单;
numpy.save():将数组保存到以.npy为扩展名的文件中,文件中的数据是乱码的,因为是Numpy专用的二进制格式化后的数据;
numpy.savez():将多个数组保存到以.npz为扩展名的文件中;
numpy.load():读取以.npy文件为扩展名的数据。
四、双端队列
借助.append、.pop方法,列表也可以当做栈或队列使用,但是在列表头部插入或删除项有一定开销,因为整个列表必须在内存中移动。collections.deque类实现一种线程安全的双端队列,旨在快速在两端插入和删除项(近似O(1)的性能),但从deque对象中部删除项的速度并不快。
deque对象可以固定长度,在对象被填满后,从一端添加新项,将从另一端丢弃另一项,这是实现保留“最后几项”或类似操作的唯一选择。
示例:展示deque对象可执行的一些典型操作
from collections import deque
# maxlen为deque的限制长度
dq = deque(range(10), maxlen=10)
print(dq) # output: deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
# 轮转:num>0,从右端取num项放到左端;num<0,从左端取num项放到右端
dq.rotate(3)
print(dq) # output: deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
dq.rotate(-4)
print(dq) # output: deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
# 左端添加元素-1,此时向已满,左端端添加几项,右端就要舍弃几项
dq.appendleft(-1)
print(dq) # output: deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
# extend(iter)依次将iter中的元素追加到deque右端
dq.extend([11, 22, 33])
print(dq) # output: deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
# extendleft(iter)依次将iter中的元素追加到deque左端
dq.extendleft([10, 20, 30, 40])
print(dq) # output: deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)
list和deque之间的方法:
deque实现了多数list方法,另外增加了专用方法,如:popleft和rotate。
deque中的append和popleft是原子操作(执行时不会被打断,执行前后系统状态保持一致),因此可以放心的在多线程应用中把deque作为先进先出队列使用,无须加锁。
五、其他队列
除deque外,python标准库中的其他包还实现了以下队列:
1.queue
实现了面向多生产线程、多消费线程的队列。提供了几个同步队列类,可用于构建多线程应用程序:
simpleQueue:无界的先进先出队列构造函数,缺少任务跟踪等高级功能;
Queue:有界的先进先出队列;
LifoQueue:有界的后进先出队列;
PriorityQueue:有界的先级队列,按照级别顺序取出元素,级别低的最先取出。
注:queue提供的有界队列与deque的有界不同,它们不像deque那样为了腾出空间而把项丢弃,而是在队列填满后阻塞插入新项,等待其他线程从队列中取出一项。
2.multiprocessing
实现了面向多生产进程、多消费进程的队列。该模块单独实现了无界的simpleQueue和有界的Queue。
与queue.Queue的区别:
queue.Queue是进程内用的队列,是多线程的
multiprocessing.Queue是跨进程通信队列,是多进程的
3.asyncio
实现了面向多生产协程、多消费协程的队列,提供了Queue、PriorityQueue、LifoQueue和JoinableQueue,API源自queue和multiprocessing模块中的类,但是为管理异步编程任务做了修改。
4.heapq
与前三个模块相比,heapq并没有实现任何队列类,但是提供了一系列函数可把可变序列当作堆队列(小顶堆)或优先级队列使用。
heapq相关函数:
heappush(heap,num):先创建一个空堆,然后将数据一个一个添加到堆中,每添加一个数据后,heap都满足小顶堆的特性;
heapify(array):直接将数据列表调整成一个小顶堆;
heappop(heap):将堆顶的数据出堆,并将堆中剩余的数据构造成新的小顶堆;
nlargest(num,heap):从堆中取出num个元素,从最大的数据开始取,返回一个列表;
nsmallest(num,heap):从堆中取出num个元素,从最小的数据开始取,返回一个列表。