Python array模块基础教程:创建数值数组及基本操作
本教程详细介绍 Python 标准库 array 模块的核心原理与用法,涵盖同质数值数组的创建方法、类型码规范、基础增删改查操作以及高效的二进制读写技巧。
假设你正在处理传感器采集的实时温度流,或者需要把几百万个整型编号暂存在内存中进行去重与清洗。很多开发者的第一反应是直接使用 Python 内置的 list。然而,当数据量达到百万甚至千万级别时,你会发现系统的内存占用呈爆发式增长,甚至触发系统的内存告警。这是因为 Python 列表本质上是指针数组,列表里的每个数字都被包装成独立的 Python 对象,带来了沉重的头部开销。如果你的项目运行在无三方库依赖的精简生产环境或微容器中,无法安装 NumPy,那么 Python 原生自带的 array 模块就是解决这一痛点的最佳利器。本文将系统讲解 array 模块的工作机制、类型码规范、数组创建方式、核心操作以及二进制序列化技巧。
一、理解 array 模块:同质存储与内存优势
Python 标准库中的 array 模块提供了一种紧凑的数组类型 array.array。与可以容纳任意类型混合元素的 list 不同,array.array 要求数组内存储的所有元素必须是完全相同的基本 C 语言数值类型(例如定长整数或浮点数)。
在底层实现中,Python 的 list 保存的是指向各个对象的 64 位指针(在 64 位系统上)。每个被指向的整数(PyLongObject)本身除了实际数值外,还要存储引用计数(8 字节)和类型指针(8 字节),单个小整数往往占用 28 字节甚至更多内存。而 array.array 仅仅维护一个简短的描述头部,底层则直接分配一段连续的原生 C 语言数组内存空间。如果是 32 位整数(类型码 'i'),每个元素只严格占用物理上的 4 个字节,不产生任何额外的对象封装开销。
二、核心基础:类型码(Type Code)对照表
创建 array.array 对象时,第一个强制参数就是类型码(Type Code)。类型码是一个单字符字符串,用于严格约束数组中每个数值的 C 底层类型、字节宽度以及符号类型(有符号或无符号)。
常用类型码如下表所示:
| 类型码 | C 对应类型 | Python 等价类型 | 最小字节大小 | 数值取值范围 |
|---|---|---|---|---|
'b' |
signed char | int | 1 | -128 至 127 |
'B' |
unsigned char | int | 1 | 0 至 255 |
'h' |
signed short | int | 2 | -32,768 至 32,767 |
'H' |
unsigned short | int | 2 | 0 至 65,535 |
'i' |
signed int | int | 2(现代平台普遍为 4) | -2,147,483,648 至 2,147,483,647 |
'I' |
unsigned int | int | 2(现代平台普遍为 4) | 0 至 4,294,967,295 |
'l' / 'L' |
signed / unsigned long | int | 4(64位系统通常为 8) | 对应长整型范围 |
'q' / 'Q' |
signed / unsigned long long | int | 8 | -9,223,372,036,854,775,808 至 9,223,372,036,854,775,807 |
'f' |
float | float | 4 | 单精度 IEEE 754 浮点数 |
'd' |
double | float | 8 | 双精度 IEEE 754 浮点数(Python float 默认等价) |
与 Python 动态类型列表的自动大整数升级机制不同,array 严格遵从类型码限定。如果你试图向类型码为 'B'(0~255)的数组中写入数字 300 或 -1,Python 会立即抛出 OverflowError;如果写入浮点数,则会抛出 TypeError。
三、创建数值数组的步骤与方法
在 Python 中创建 array.array 实例的规范语法为:
array.array(typecode[, initializer])
其中 typecode 为上述表格中的字符,initializer 为可选的初始数据序列(如列表、元组或可迭代生成器)。完整创建通常经过规范的四步流程:
3.1 从现有列表或元组创建
这是最直观的初始化方式。只需把一个数值列表作为第二个参数传入构造函数:
import array
# 创建 32 位有符号整数数组
int_arr = array.array('i', [100, 200, 300, 400])
print("整型数组:", int_arr)
# 创建双精度浮点数数组
float_arr = array.array('d', [1.25, 3.14159, 9.8])
print("浮点数组:", float_arr)
示例输出:
整型数组: array('i', [100, 200, 300, 400])
浮点数组: array('d', [1.25, 3.14159, 9.8])
3.2 创建空数组并动态扩充
如果不传入初始化序列,或者传入空列表 [],即可得到一个对应类型码的空数组:
import array
empty_arr = array.array('h') # 空的 16 位整型数组
empty_arr.append(50)
empty_arr.append(120)
print("动态填充后的数组:", empty_arr)
3.3 使用生成器表达式构建大型序列
如果需要初始化一段范围极大的序列,直接生成包含千万数据的普通列表会先在内存中产生庞大的临时对象。推荐使用生成器表达式直接喂给 array.array,从而实现流式构建,避免瞬时内存峰值:
import array
# 0 到 999 的偶数数组,通过生成器表达式流式构造
even_arr = array.array('I', (x for x in range(1000) if x % 2 == 0))
print("前5个元素:", even_arr[:5])
print("数组元素总数:", len(even_arr))
四、数值数组的基本操作体系
array.array 实现了 Python 序列协议的大部分常用方法,使用习惯与普通列表高度一致,支持索引读取、切片截取、插入删除等常见操作。
4.1 访问、修改与切片
你可以通过下标直接访问或修改元素。切片操作将返回一个相同类型码的新 array 对象:
import array
numbers = array.array('i', [10, 20, 30, 40, 50])
# 索引访问
print("首个元素:", numbers[0]) # 输出: 10
print("最后一个元素:", numbers[-1]) # 输出: 50
# 元素修改
numbers[1] = 99
print("修改后数组:", numbers) # array('i', [10, 99, 30, 40, 50])
# 切片截取(步长与区间)
sub_slice = numbers[1:4]
print("切片结果:", sub_slice) # array('i', [99, 30, 40])
4.2 添加与插入元素
向数组追加数据时,支持单元素追加与批量追加:
import array
arr = array.array('i', [1, 2, 3])
# append: 追加单个元素
arr.append(4)
# extend: 接收任意同质可迭代对象批量追加
arr.extend([5, 6])
# insert: 在指定位置插入元素(注意:插入操作会引发后续元素物理位移)
arr.insert(0, 0) # 在索引 0 处插入 0
print("更新后的数组:", arr) # array('i', [0, 1, 2, 3, 4, 5, 6])
4.3 删除与弹出元素
删除操作包含 pop() 和 remove():
import array
arr = array.array('i', [10, 20, 30, 20, 40])
# pop: 弹出指定索引元素(默认最后一个)并返回该值
val = arr.pop()
print("弹出末尾值:", val) # 输出: 40
# remove: 删除数组中首次出现的指定数值
arr.remove(20)
print("移除20后数组:", arr) # array('i', [10, 30, 20])
4.4 检查属性与内存信息
array 对象提供了两个非常实用的属性:itemsize(单个元素的物理字节数)和 buffer_info()(底层内存地址与元素计数):
import array
data = array.array('d', [1.1, 2.2, 3.3])
print("类型码:", data.typecode) # 'd'
print("单元素字节大小:", data.itemsize) # 8
# buffer_info() 返回一个二元组 (内存缓冲区虚拟地址, 元素个数)
address, length = data.buffer_info()
print(f"内存首地址: {hex(address)}, 元素数量: {length}")
print(f"实际物理内存占用: {length * data.itemsize} 字节")
五、进阶实战:超高速二进制文件与字节流 I/O
在数据处理中,如果使用标准文本格式(如 JSON、CSV)存储几千万个数值,会消耗大量 CPU 时间进行字符串格式化和解析。array 模块最具威力的特性是直接支持与原生二进制流双向互通。
因为 array 内部就是一段平坦的 C 连续内存,它的 tofile() 方法能够直接调用底层 C 语言的 fwrite(),把这段内存按字节原封不动写入磁盘,绕过所有 Python 对象处理层;同理,fromfile() 也是直接通过 fread() 瞬时加载。
import array
import os
# 1. 创建包含 1,000,000 个 32位整型的数组
arr_write = array.array('i', range(1000000))
# 2. 以二进制写入模式保存到文件(体积仅约 3.81 MB)
file_path = "numbers.bin"
with open(file_path, "wb") as f:
arr_write.tofile(f)
print(f"文件已生成,物理磁盘大小: {os.path.getsize(file_path)} 字节")
# 3. 极速读取:先建立同类型的空数组,再使用 fromfile 直接灌入
arr_read = array.array('i')
with open(file_path, "rb") as f:
# 从文件中精确读取 1000000 个 'i' 元素
arr_read.fromfile(f, 1000000)
print("成功还原元素数量:", len(arr_read))
print("校验数据正确性:", arr_read[:5] == arr_write[:5])
# 清理测试文件
if os.path.exists(file_path):
os.remove(file_path)
除了磁盘文件,你还可以通过 tobytes() 将数组转换为标准的 Python bytes 对象,方便通过 Socket 套接字在网络中传输,接收方再通过 frombytes() 毫秒级复原。
六、选型决策:list、array 与 numpy 的差异与边界
在日常开发中,究竟何时该用 list、array 还是 numpy.ndarray?三者的边界与取舍可以通过以下决策模型清晰展现:
常见注意事项与避坑指南
- 不支持向量化数学运算: 与普通
list类似,直接对array执行arr * 2不会使数组内的数值翻倍,而是将整个数组复制拼接两遍。如果你需要频繁进行矩阵乘法、向量加法等复杂数值计算,应选用 NumPy。 - 类型码平台可移植性: 类型码如
'i'或'l'的实际宽度遵循所在平台的 C 编译器标准。虽然现代 64 位操作系统中'i'基本都是 4 字节,但如果需要在不同机器架构间通过二进制文件无损交换数据,推荐优先使用定宽类型码(如'q'/'Q'为确定 8 字节)或通过标准协议标明字节序。 - 不支持嵌套多维数组:
array.array本质是一维线性连续空间,不能像list那样直接构建[[1, 2], [3, 4]]的嵌套多层数组。如需表示二维网格,通常需要手动计算展平索引index = row * width + col。
七、总结
Python 标准库的 array 模块为开发者提供了一个轻盈而强大的原生数值存储方案。在不需要庞大第三方依赖的场景下,通过精确指定类型码,array.array 能够有效剔除 Python 对象指针模型的臃肿开销,将数百万级数值的内存占用压缩至极致,并带来极具优势的二进制文件 I/O 吞吐能力。掌握类型码的边界与基本操作,能让你在处理高频数字数据流与资源敏感型应用时更加得心应手。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









