Python list和array模块有什么区别?内存占用与性能对比

Cora
Cora
发布时间:2026-10-28| 10人浏览过
原创

深入对比Python内置list与标准库array模块的底层实现差异,通过内存布局解析、真实数据基准测试、操作耗时量化以及选型决策树,全面解析两者的内存占用与运行性能。

Python 的 list 是基于指针数组实现的通用动态容器,能够存储任意异构对象,开发极其灵活但存在显著的内存与指针间接寻址开销;标准库的 array 模块则是基于 C 语言原生数据类型的紧凑单维连续数组,必须存储同构的基础数值类型。当存储数百万级基础数值时,array 占用的内存通常只有 list 的四分之一到十分之一,但在纯 Python 循环遍历时由于频繁的装箱与拆箱操作,其执行速度可能反而略慢于已解析的对象指针列表。

一、底层实现机制:指针数组与原生连续缓冲区的本质分歧

很多 Python 初学者容易将 list 当成普通数组看待,但从 CPython 解释器的底层 C 结构实现来看,两者存在根本性的架构差异:

  • list(PyListObject):本质是一个存储 PyObject* 对象指针的动态连续数组。无论向列表中存放整数、浮点数还是字符串,list 内部直接存储的都不是具体的数值,而是指向各个对象在堆内存中独立分配位置的 64 位内存指针。每个 Python 整数本身都是一个完整的 PyLongObject,包含引用计数、类型指针以及分块存储的数值,即使是最基础的整数 0,在 64 位系统上也额外背负着 28 字节的元数据开销。
  • array(arrayobject):属于 Python 标准库内置模块,其内部开辟的是一块连续的原始 C 语言数据缓冲区(如同 C 语言的 int[] 或 double[])。如果在声明时指定类型码为 'i'(带符号 4 字节整数),那么每个数组元素就实打实地只占用连续内存中的 4 个字节,没有任何额外的指针引用,也没有散落在堆上的独立对象头。
Python list和array模块有什么区别?内存占用与性能对比
图 1:Python list 的指针间接引用结构与 array 的连续原生存储结构

二、内存占用量化对比:1000 万个整数的实测分析

为了直观感受内存差异,我们设计一个量化测试:分别使用 list 和 array 存储 10,000,000(一千万)个 32 位整型数字,使用标准库的 sys.getsizeof() 观察容器自身及其所维护的数据开销。

Python list和array模块有什么区别?内存占用与性能对比
图 2:测量与评估 Python 序列数据性能的标准化步骤

下面是可复现的测试脚本,用于量化 1000 万个整数的内存占用:

import sys
import array

N = 10_000_000

# 1. 测量 array.array('i') 的内存占用('i' 代表 4 字节 signed int)
arr = array.array('i', range(N))
arr_mem = sys.getsizeof(arr)

# 2. 测量 list 的指针数组占用
lst = list(range(N))
lst_ptr_mem = sys.getsizeof(lst)

# 单个独立整数(大于 256,避开小整数对象池)的开销
sample_int = 10_000_001
int_obj_size = sys.getsizeof(sample_int)

print(f"array('i') 自身总占用: {arr_mem / 1024 / 1024:.2f} MB")
print(f"list 自身指针数组占用: {lst_ptr_mem / 1024 / 1024:.2f} MB")
print(f"单个 int 对象大小: {int_obj_size} 字节")
print(f"list 若全为独立整数预估总内存: {(lst_ptr_mem + N * int_obj_size) / 1024 / 1024:.2f} MB")

在 64 位 CPython 3.11/3.12 环境下,输出结果如下(实际数字会随小版本微调):

# 示例输出:
array('i') 自身总占用: 38.15 MB
list 自身指针数组占用: 76.29 MB
单个 int 对象大小: 28 字节
list 若全为独立整数预估总内存: 343.32 MB

数据解读:

  1. array('i'):1000 万个 4 字节整数精确需要 10,000,000 * 4 = 40,000,000 字节,加上结构体头部几十字节的元数据,最终占用约为 38.15 MB。
  2. list 指针数组:由于存放 1000 万个 64 位指针(每个指针 8 字节),再加上 list 自身的预分配冗余容量,仅存放指针就需要约 76.29 MB。这还没算任何整数数据本身的体积。
  3. 整体内存:如果这 1000 万个数据是运算过程中产生的独立非共享整数(每个 28 字节),list 加上所有被引用的整数对象,系统堆总内存占用将飙升至 340 MB 以上。相比之下,array 实现了近 9 倍的内存节约。

三、性能表现与耗时对比:装箱与拆箱的双刃剑

许多开发者直觉认为“array 既然底层是连续 C 数组,那么计算和遍历速度一定比 list 快得多”。但在实际测试中,结论往往出人意料:在纯 Python 循环中遍历和累加元素,list 经常比 array 更快。

为什么会出现这种反直觉的现象?核心原因在于 Python 虚拟机的装箱(Boxing)与拆箱(Unboxing)开销。

Python list和array模块有什么区别?内存占用与性能对比
图 3:元素取值时的装箱拆箱机制对执行性能的影响

我们使用 timeit 针对常见操作进行量化基准测试:

import timeit
import array

setup_code = """
import array
N = 1_000_000
lst = list(range(N))
arr = array.array('i', range(N))
"""

# 1. 遍历并求和
t_list_sum = timeit.timeit("sum(lst)", setup=setup_code, number=30)
t_arr_sum = timeit.timeit("sum(arr)", setup=setup_code, number=30)

# 2. 索引读取
t_list_idx = timeit.timeit("[lst[i] for i in range(10000)]", setup=setup_code, number=100)
t_arr_idx = timeit.timeit("[arr[i] for i in range(10000)]", setup=setup_code, number=100)

print(f"30次 sum(lst) 耗时: {t_list_sum:.4f} 秒")
print(f"30次 sum(arr) 耗时: {t_arr_sum:.4f} 秒")
print(f"100次 循环索引读取 list 耗时: {t_list_idx:.4f} 秒")
print(f"100次 循环索引读取 array 耗时: {t_arr_idx:.4f} 秒")

运行结果(典型性能特征):

# 示例输出:
30次 sum(lst) 耗时: 0.1852 秒
30次 sum(arr) 耗时: 0.3120 秒
100次 循环索引读取 list 耗时: 0.0410 秒
100次 循环索引读取 array 耗时: 0.0768 秒

测试证实:在 Python 级别的 for 循环或内置函数迭代中,array 的元素必须临时经历 C 标量到 Python int 对象的构造过程,导致其耗时约为 list 的 1.5 到 2 倍。

然而,在二进制 IO 场景下 array 具备绝对优势:

如果业务涉及到将海量数值序列持久化到磁盘,或通过网络套接字传输:

  • list 必须使用 pickle 序列化,或者遍历写入文本(CSV/JSON),伴随着巨大的序列化与字符串解析开销;
  • array 提供了原生的 arr.tofile(f) 和 arr.fromfile(f, n) 方法,其底层实现就是 C 标准库的 fwrite 和 fread,直接将内存块与磁盘进行无损 memcpy 复制,速度通常比 pickle 快 10 倍以上,比文本解析快数十倍,生成的文件体积也严格紧凑。
import array
import time

arr = array.array('i', range(5_000_000))

# 快速二进制落盘
start = time.perf_counter()
with open("data.bin", "wb") as f:
    arr.tofile(f)
write_time = time.perf_counter() - start

# 快速二进制恢复
arr_in = array.array('i')
start = time.perf_counter()
with open("data.bin", "rb") as f:
    arr_in.fromfile(f, 5_000_000)
read_time = time.perf_counter() - start

print(f"500万个整数写入耗时: {write_time:.4f} 秒")
print(f"500万个整数读取耗时: {read_time:.4f} 秒")
# 示例输出:
500万个整数写入耗时: 0.0125 秒
500万个整数读取耗时: 0.0098 秒

四、功能特性与类型码支持

array 的紧凑性来源于强类型约束。在初始化 array 时,必须显式传入一个字符作为类型码(Type Code),指定每个元素在底层 C 结构中的数据类型:

类型码 对应 C 类型 标准字节大小 取值范围与适用说明
'b' / 'B' signed / unsigned char 1 字节 -128~127 或 0~255,常用于字节流、灰度像素
'h' / 'H' signed / unsigned short 2 字节 -32,768~32,767,音频 PCM 采样数据
'i' / 'I' signed / unsigned int 4 字节 常用 32 位整型序列
'l' / 'L' signed / unsigned long 4 或 8 字节 长整型数据(大小取决于系统架构与 C 编译器)
'q' / 'Q' signed / unsigned long long 8 字节 64 位超大整型时间戳、UUID 高低位等
'f' float 4 字节 单精度浮点数,3D 顶点坐标、图形变换参数
'd' double 8 字节 双精度浮点数,高精度科学计算与时间序列
注意类型异常拦截:如果试图向 array.array('i') 中添加浮点数或字符串,会立刻抛出 TypeError: integer argument expected, got float;如果插入的整数超出底层 C 类型的上下界,会抛出 OverflowError。这种即时校验可以在输入边界提早发现脏数据。

五、选型决策指南:list、array 与 NumPy 边界

面对这几种看似重叠的序列容器,在实际项目中该如何选择?核心取决于三个维度:数据同构性、数据规模、以及对第三方库的依赖限制。

Python list和array模块有什么区别?内存占用与性能对比
图 4:针对序列数据的最佳实践选型路径

核心选型依据:

  • 选用 list 的场景:
    • 集合规模较小(例如万级以内),代码的可读性和开发便捷度优先;
    • 需要存放异构数据(如字典、对象、字符串与数值混合);
    • 需要频繁调用诸如 sort(key=...)、切片拼接等高度灵活的内置方法。
  • 选用 array 的场景:
    • 在受限环境或纯标准库环境(如 AWS Lambda 纯净运行环境、打包体积受限的 CLI 工具、嵌入式 MicroPython)中处理数百万乃至上千万的基础数值;
    • 关注常驻内存占用,机器内存资源吃紧,不能承受 list 的指针冗余;
    • 核心任务是二进制数据读写或网络流通信(如直接读写传感器采样点、音频帧数据流),通过 tofile() / fromfile() 获取极高吞吐。
  • 为何不直接用 NumPy?
    • NumPy 是科学计算的行业标准,但它是一个体积庞大的 C/Fortran 外部二进制库。在轻量级脚本、无编译链的精简容器或对冷启动时间敏感的云函数中,引入 NumPy 会显著增加依赖体积和维护复杂度;如果你的代码仅仅需要“紧凑存储一列数字并存盘”,标准库自带的 array 是最轻量的纯天然选择。

六、常见使用注意事项

  1. 不支持多维数组:array 模块仅支持一维连续结构。如果需要二维或多维矩阵,需要自行计算一维展平索引(如 index = row * width + col),或者直接引入 NumPy。
  2. 内存视图零拷贝:如果需要对 array 的子区间进行切片而不发生内存复制,请配合 memoryview 使用(如 mv = memoryview(arr)),这能实现完全零拷贝的底层切片访问与缓冲传递。
  3. 不能在 Python 级别向量化加速:不要期望写 arr * 2 能够像 NumPy 那样直接让所有元素乘以 2;对 array 执行乘法只会像 list 一样将数组自身重复拼接成双倍长度。数学运算仍需依赖循环或高阶函数。
相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。