Python中的“数组”是什么?list、tuple与array模块的区别

拾柒
拾柒
发布时间:2026-10-10 | 2人浏览过
原创

在许多编程语言中,“数组”通常指一段在内存中连续分配、长度固定且存储同一种数据类型的数据结构。而在日常编写 Python 代码时,大家口头上说的“数组”多半是指内置的列表(list)。严格来说,Python 并没有一个名叫 array 的原生保留关键字类型,而是根据不同需求提供了三种内置或标准库形态:灵活通用的 list、不可变的安全集合 tuple,以及专门用于存储紧凑数值的 array 模块。本文将具体厘清这些概念在内存存储、可变性与实际使用场景上的差异。

一、Python 里到底有没有真正的“数组”?

严格按照计算机科学的定义来看,C 或 Java 中的原生数组具备两个硬性特征:存储的数据类型一致、元素在内存中紧凑连续排列。在 Python 中:

  • 最常被当作数组用的是 list(列表):它是动态引用数组,内部存储的不是具体数据本身,而是指向各个对象的指针。
  • tuple(元组):结构与列表类似,同样存储指针,但一旦创建就无法修改长度或替换元素,属于不可变的引用序列。
  • 标准库 array 模块:这是 Python 官方提供的“严格意义上的基础数组”。它要求所有元素必须是同一基础 C 类型(如整型、浮点型),并在内存中连续存储原始字节。

为了直观展现它们在内存管理模式上的本质区别,可以参考下方的对象与内存布局对比:

Python中的“数组”是什么?list、tuple与array模块的区别
图 1:Python 动态引用结构(list/tuple)与原生连续缓冲区(array)在内存中的物理分布对比

二、核心角色深入剖析

1. list(列表):最常用的动态引用数组

平时通过方括号 [] 创建的就是列表。列表在 Python 底层(CPython)对应 PyListObject 结构体。它包含两个核心要素:当前元素数量、预分配的指针数组(PyObject**)。

正因为列表中保存的只是指针,它可以容纳任何类型的对象:

# 列表可以混装不同类型
my_list = [100, "文本", 3.14, [1, 2]]
my_list.append(500)       # 动态追加
my_list[0] = 999          # 允许修改现有元素

注意内存机制:虽然 list 是动态的,但在增删元素时它会自动进行过量内存分配(Over-allocation),避免每次 append 都向操作系统申请内存。不过,每个元素对应的 Python 整数对象或字符串对象依然分散在堆内存各处,每次遍历都需要指针解引用。

2. tuple(元组):轻量不可变的序列

元组通过圆括号 () 创建。在底层,元组与列表的结构非常接近,但它去除了动态扩容机制,并在解释器层面限制了修改操作:

point = (10, 20)
# point[0] = 30  # 会直接抛出 TypeError: 'tuple' object does not support item assignment

因为不可变,元组具备几个关键优势:

  • 内存占用更小:无需预留扩容的空闲指针槽位,大小刚刚容纳已有元素。
  • 可用作字典的键:只要元组内部的元素全是可哈希类型,该元组就可以作为 dict 的 key 或放入 set。
  • 数据安全保护:在函数间传递不希望被调用方意外修改的配置参数、坐标值时,元组是首选。

3. array 模块:高效紧凑的基础数据数组

如果你在处理数百万个整数或浮点数,且不需要做复杂的科学矩阵计算,标准库的 array 模块就是最轻量、零依赖的解决方案。它在初始化时必须指定一个类型代码(Type Code),规定该数组存放哪种底层 C 数据类型。

以下是使用标准库 array 的完整操作过程:

import array

# 创建一个存储有符号 32 位整数(类型代码为 'i')的数组
# 常见类型代码:'b'(char), 'h'(short), 'i'(int), 'f'(float), 'd'(double)
numbers = array.array('i', [10, 20, 30, 40])

# 支持大部分类似列表的操作
numbers.append(50)
print(numbers[0])  # 输出示例: 10

# 如果试图插入不符合类型约束的元素,会立即报错
try:
    numbers.append(3.14)
except TypeError as err:
    print(f"类型检查拦截: {err}")
    # 输出示例: 类型检查拦截: 'float' object cannot be interpreted as an integer

下面这幅流程图展示了在 Python 解释器中向 array.array 插入元素时的校验与存储执行步骤:

Python中的“数组”是什么?list、tuple与array模块的区别
图 2:array 模块元素写入时的类型安全核验与内存追加流程

三、特性全面对比

了解三者的底层原理后,我们可以从日常开发的实际维度总结它们的区别:

特性维度 list(列表) tuple(元组) array 模块
语法与引入 内置语法 [] 内置语法 () 标准库需 import array
元素类型 任意对象,可异构混合 任意对象,可异构混合 同质基础数值(由类型代码限定)
可变性 (Mutable) 可变(可修改、增删) 不可变(无法添加或直接修改) 可变(同类型下可追加、替换)
内存存放方式 对象指针的连续数组 对象指针的定长连续数组 基础数值的直接二进制连续块
内存占用 较大(含多余预留槽与对象头) 较小(紧凑结构,无空闲槽) 极小(纯物理数值紧密排布)
字典键支持 不可作为 key 元素均可哈希时可作为 key 不可作为 key

四、选型判断:在什么时候用哪一个?

很多开发者会产生疑惑:既然 array 模块省内存,为什么平时很少看到别人用?

原因在于应用生态的平衡。如果数据量只有几千个,list 的内存开销对现代电脑而言几乎可以忽略,而它支持任意类型、语法简单、生态方法极其丰富;如果涉及上百万、上亿个纯数值的科学计算或矩阵运算,Python 开发者通常会直接选用第三方库 numpy.ndarray,因为 NumPy 不仅连续存储,还提供了 C 语言级的多维向量运算支持。因此,内置的 array 模块更适合标准库环境下对大批量数值进行文件读写、网络二进制传输或受限硬件环境。

下图展示了面对不同业务需求时的选型决策路径:

Python中的“数组”是什么?list、tuple与array模块的区别
图 3:针对不同业务场景的容器选型逻辑图

五、实际使用中的常见陷阱与注意事项

陷阱 1:元组中的“可变对象”依然可变
元组的不可变性仅针对自身包含的指针槽位。如果元组内包含一个列表,例如 t = (1, [2, 3]),虽然你不能给 t[1] 重新赋值指向新列表,但你依然可以通过 t[1].append(4) 修改列表内部的内容。此时该元组是不可哈希的,不能作为字典的键。
陷阱 2:array 模块不支持多维嵌套
与 list 不同,array.array 只能保存一维的一组数据。如果你需要声明二维矩阵(例如 3×3 矩阵),不能在 array 里面继续嵌套 array,这时应当使用嵌套列表,或者直接引入 numpy。
陷阱 3:列表乘法引发的浅拷贝陷阱
使用 [[0] * 3] * 3 创建所谓“二维数组”时,外部列表存储的是同一个内层列表的 3 个重复指针。修改其中一行会导致所有行同步被修改。推荐使用列表推导式:[[0] * 3 for _ in range(3)]。

六、知识全貌总结

总结来说,Python 中日常说的“数组”是一个泛指的口头表述。在理解 Python 基础时,只需要抓住一个核心:Python 没有关键字形式的数组,只有满足不同底层特性的序列容器。

Python中的“数组”是什么?list、tuple与array模块的区别
图 4:list、tuple 与 array 模块的核心特征与定位结构全览

日常写业务代码时,默认选用 list 即可;如果要声明静态配置或充当字典键,选用 tuple;当面临内存瓶颈且限制只能使用标准库存储大量整数或浮点数时,再考虑使用 array 模块。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表