Python 数组介绍:基础知识 – wiki基地


Python 数组介绍:基础知识深度解析

在编程领域,数组(Array)是一种非常基础且重要的数据结构。它通常被定义为存储同类型数据元素的集合,这些元素在内存中是连续存储的,并且可以通过一个唯一的索引(通常是整数)来访问。数组因其高效的随机访问能力而在许多计算任务中扮演着核心角色。

然而,对于初学者来说,Python 在数组这个概念上可能显得有些独特和令人困惑。不同于 C、Java 等语言拥有内置的、低级别的数组类型,Python 标准库并没有一个叫做 array 的原始数据类型。取而代之的是,Python 提供了多种数据结构,它们在不同的场景下可以扮演“数组”的角色。其中,最常用的是 list(列表),而标准库中的 array 模块则提供了一种更接近传统数组概念的数据结构。此外,对于科学计算和数据分析,NumPy 库提供了功能强大且高效的 ndarray(N-dimensional array)。

本文将重点介绍 Python 标准库中的 array 模块,并与 Python 中最常用的序列类型 list 进行比较,同时也会简要提及 NumPy 数组,帮助读者理解 Python 中“数组”的多样性及其基础知识。

1. 理解传统数组概念及其优势

在深入 Python 之前,让我们先回顾一下传统编程语言中数组的核心概念和优势:

  1. 同质性 (Homogeneity):传统数组通常要求所有元素都是同一数据类型(例如,一个整数数组只能存储整数,一个浮点数数组只能存储浮点数)。
  2. 连续存储 (Contiguous Memory):数组的元素在内存中是紧密排列、连续存放的。
  3. 固定大小 (Fixed Size):一旦创建,传统数组的大小通常是固定的,不能随意改变。
  4. 高效随机访问 (Efficient Random Access):由于元素是同质且连续存储的,可以通过简单的数学计算(基地址 + 索引 * 元素大小)快速定位并访问任何位置的元素,其时间复杂度为 O(1)。
  5. 内存效率 (Memory Efficiency):由于不需要为每个元素存储额外的类型信息或指向下一个元素的指针(像链表那样),同质数组通常比存储异质元素的动态数组更节省内存。

这些特性使得传统数组在需要存储大量同类型数据且对访问速度有较高要求的场景下表现出色,例如图像处理、音频处理、数值计算等。

2. Python 中的 list:灵活但非传统数组

在 Python 中,list 是最常用的序列类型,它经常被用来存储一组有序的数据。从表面上看,list 似乎很像数组,因为它支持索引访问和切片操作。然而,list 的内部实现与传统数组有本质区别:

  1. 异质性 (Heterogeneity):Python 的 list 可以存储不同数据类型的元素(整数、字符串、对象等)。
  2. 动态大小 (Dynamic Size)list 的大小是可变的,可以方便地添加或删除元素。
  3. 非严格连续存储 (Less Strictly Contiguous):虽然 Python 尝试将 list 的元素引用存储在连续的内存块中,但这些元素本身(存储的实际数据)可能分散在内存各处。list 实际上存储的是指向这些对象的指针。
  4. 内存开销 (Memory Overhead):由于需要存储每个元素的类型信息(即使是引用)以及 list 自身的元信息(大小、已分配容量等),list 通常比存储同类型数据的传统数组占用更多内存。

正因为 list 的这些特性,它更加灵活和通用,适合处理各种类型和大小的数据集合。但在某些对性能和内存要求极高的场景下,尤其是需要处理大量数值数据时,list 的开销可能会成为瓶颈。

3. Python 标准库 array 模块:更接近传统数组

为了在 Python 中提供一种更接近传统数组、能够存储同质、类型受限数据并更节省内存的机制,Python 标准库提供了 array 模块。这个模块定义了一个 array 类型对象,它是一个值的序列,并且这些值必须是同一种基本数据类型。

3.1 创建 array 对象

要使用 array 模块,首先需要导入它:

python
import array

创建 array 对象时,需要指定两个参数:元素的类型代码 (type code) 和一个初始化器(通常是一个可迭代对象,如列表或元组)。

类型代码是一个字符,用来指定数组中元素的 C 语言类型。常见的类型代码包括:

类型代码 C 类型 Python 类型 最小字节数
'b' signed char int 1
'B' unsigned char int 1
'u' Py_UNICODE Unicode Character 2 or 4
'h' signed short int 2
'H' unsigned short int 2
'i' signed int int 2 or 4
'I' unsigned int int 2 or 4
'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

注意:'u' 类型代码在 Python 3.3 后已弃用,推荐使用字符串。整数类型的字节数可能因系统而异,但 array 模块会使用系统默认的大小。'q''Q' 在支持 64 位整数的平台上可用。

示例:创建不同类型的数组

“`python
import array

创建一个存储整数(signed int)的数组

类型代码 ‘i’

int_array = array.array(‘i’, [1, 2, 3, 4, 5])
print(“Integer Array:”, int_array)
print(“Type Code:”, int_array.typecode)
print(“Item Size (bytes):”, int_array.itemsize) # 每个元素占用的字节数

创建一个存储浮点数(double)的数组

类型代码 ‘d’

float_array = array.array(‘d’, [1.1, 2.2, 3.3, 4.4, 5.5])
print(“Float Array:”, float_array)
print(“Type Code:”, float_array.typecode)
print(“Item Size (bytes):”, float_array.itemsize)

创建一个存储无符号字符(unsigned char)的数组

类型代码 ‘B’

byte_array = array.array(‘B’, [65, 66, 67, 68, 69]) # ASCII values for A, B, C, D, E
print(“Byte Array:”, byte_array)
print(“Type Code:”, byte_array.typecode)
print(“Item Size (bytes):”, byte_array.itemsize)

创建一个空数组

empty_array = array.array(‘h’) # signed short
print(“Empty Array:”, empty_array)
“`

在创建数组时,如果初始化器中的元素与指定的类型不兼容,将会引发 TypeError

“`python

尝试向整数数组添加浮点数(会导致错误)

try:
invalid_array = array.array(‘i’, [1, 2, 3.5, 4])
except TypeError as e:
print(f”Error creating array: {e}”)

尝试向浮点数数组添加非数字类型(会导致错误)

try:
another_invalid_array = array.array(‘f’, [1.1, 2.2, ‘hello’])
except TypeError as e:
print(f”Error creating array: {e}”)
“`

这体现了 array 对象的同质性要求。

3.2 访问和修改元素

array 对象支持与 list 类似的索引和切片操作来访问和修改元素。索引从 0 开始。

“`python
import array

my_array = array.array(‘i’, [10, 20, 30, 40, 50])

访问单个元素

print(“First element:”, my_array[0])
print(“Third element:”, my_array[2])
print(“Last element:”, my_array[-1])

访问超出范围的索引会引发 IndexError

try:
print(my_array[10])
except IndexError as e:
print(f”Access error: {e}”)

切片访问

print(“Elements from index 1 to 3:”, my_array[1:4]) # 索引 1, 2, 3
print(“First three elements:”, my_array[:3])
print(“Elements from index 2 onwards:”, my_array[2:])
print(“Every second element:”, my_array[::2])
print(“Reversed array:”, my_array[::-1])

修改单个元素

my_array[0] = 100
print(“Modified array:”, my_array)

修改切片 (注意:切片赋值要求赋值的序列类型与原数组类型兼容)

my_array[1:3] = array.array(‘i’, [200, 300]) # 使用array类型赋值
print(“After slicing modification (array):”, my_array)

也可以使用list赋值,只要元素类型兼容

my_array[1:3] = [2000, 3000]
print(“After slicing modification (list):”, my_array)

尝试使用不兼容类型的序列进行切片赋值会引发 TypeError

try:
my_array[1:3] = [200.5, 300.5] # 浮点数赋值给整数数组
except TypeError as e:
print(f”Slicing modification error: {e}”)

尝试使用长度不匹配的序列进行切片赋值 (注意:这与 list 的切片赋值行为类似)

my_array[1:3] = [99] # 将两个元素替换为一个元素

print(“After slicing modification (length mismatch):”, my_array) # 运行正常,数组大小会调整

尝试使用不兼容类型的序列进行切片赋值 (长度匹配但类型不对)

try:
my_array[1:3] = [‘a’, ‘b’]
except TypeError as e:
print(f”Slicing modification error: {e}”)
“`

list 不同的是,array 的切片赋值要求赋值序列中的元素类型必须与数组本身的类型兼容。

3.3 添加元素

可以使用 append(), extend(), 和 insert() 方法向 array 添加元素。添加的元素必须与数组的类型兼容。

“`python
import array

my_array = array.array(‘i’, [10, 20, 30])

append(x): 在数组末尾添加一个元素 x

my_array.append(40)
print(“After append(40):”, my_array)

extend(iterable): 在数组末尾添加一个可迭代对象中的所有元素

可迭代对象可以是 list, array, tuple 等

my_array.extend([50, 60]) # 使用 list extend
print(“After extend([50, 60]):”, my_array)

other_array = array.array(‘i’, [70, 80])
my_array.extend(other_array) # 使用另一个 array extend
print(“After extend(other_array):”, my_array)

insert(i, x): 在指定索引 i 之前插入元素 x

my_array.insert(0, 5) # 在开头插入
print(“After insert(0, 5):”, my_array)
my_array.insert(3, 25) # 在索引 3 之前插入
print(“After insert(3, 25):”, my_array)

尝试添加类型不兼容的元素会引发 TypeError

try:
my_array.append(90.5)
except TypeError as e:
print(f”Append error: {e}”)

try:
my_array.extend([90.5, 91.5])
except TypeError as e:
print(f”Extend error: {e}”)

try:
my_array.insert(0, 99.9)
except TypeError as e:
print(f”Insert error: {e}”)
“`

3.4 删除元素

可以使用 pop()remove() 方法删除 array 中的元素。

“`python
import array

my_array = array.array(‘i’, [10, 20, 30, 40, 50, 20])

pop([i]): 删除并返回指定索引 i 的元素。如果未指定索引,则删除并返回最后一个元素。

popped_element = my_array.pop() # 删除并返回最后一个元素 (50)
print(“Popped element:”, popped_element)
print(“Array after pop():”, my_array)

popped_element = my_array.pop(1) # 删除并返回索引 1 的元素 (20)
print(“Popped element at index 1:”, popped_element)
print(“Array after pop(1):”, my_array)

尝试弹出超出范围的索引会引发 IndexError

try:
my_array.pop(100)
except IndexError as e:
print(f”Pop error: {e}”)

remove(x): 删除数组中第一个匹配到的元素 x。

my_array.remove(40)
print(“Array after remove(40):”, my_array)

my_array.remove(20) # 删除第一个匹配到的 20
print(“Array after remove(20):”, my_array)

尝试删除不存在的元素会引发 ValueError

try:
my_array.remove(999)
except ValueError as e:
print(f”Remove error: {e}”)
“`

3.5 数组信息和遍历

array 对象支持 len() 函数获取长度,可以使用 index()count() 方法查找元素,并且是可迭代的。

“`python
import array

my_array = array.array(‘i’, [10, 20, 30, 40, 20, 50])

len(): 获取数组长度

print(“Length of array:”, len(my_array))

index(x[, start[, end]]): 返回第一个匹配到的元素 x 的索引。

可以指定搜索的起始和结束索引。

print(“Index of first 20:”, my_array.index(20))
print(“Index of 20 starting from index 3:”, my_array.index(20, 3)) # 从索引 3 开始搜索

尝试查找不存在的元素会引发 ValueError

try:
my_array.index(999)
except ValueError as e:
print(f”Index error: {e}”)

count(x): 返回元素 x 在数组中出现的次数。

print(“Count of 20:”, my_array.count(20))
print(“Count of 10:”, my_array.count(10))
print(“Count of 999:”, my_array.count(999)) # 不存在的元素返回 0

遍历数组

print(“Iterating through array:”)
for element in my_array:
print(element, end=” “)
print()

也可以使用索引遍历

print(“Iterating using indices:”)
for i in range(len(my_array)):
print(my_array[i], end=” “)
print()
“`

3.6 转换与文件操作

array 对象可以方便地与 list 或字节序列相互转换,并且支持直接从文件读取或写入二进制数据。

“`python
import array
import os

my_array = array.array(‘i’, [1, 2, 3, 4, 5])

tolist(): 将 array 转换为 list

my_list = my_array.tolist()
print(“Converted to list:”, my_list)
print(“Type of my_list:”, type(my_list))

fromlist(list): 从一个 list 添加元素到 array

new_array = array.array(‘i’)
new_array.fromlist([6, 7, 8])
print(“New array from list:”, new_array)

注意:fromlist 也要求 list 中的元素类型兼容

try:
new_array.fromlist([9, 10.5])
except TypeError as e:
print(f”fromlist error: {e}”)

tobytes(): 将 array 转换为 bytes (Python 3.2+)

my_bytes = my_array.tobytes()
print(“Converted to bytes:”, my_bytes)
print(“Type of my_bytes:”, type(my_bytes))

frombytes(bytes): 从 bytes 读取数据到 array (Python 3.2+)

需要注意字节序 (endianness)

new_array_from_bytes = array.array(‘i’)

假设系统是小端字节序 (little-endian)

new_array_from_bytes.frombytes(b’\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00′) # 3 * sizeof(int)=12 bytes
print(“New array from bytes:”, new_array_from_bytes)

tofile(f): 将所有元素以机器值的形式写入文件 f (一个二进制文件对象)

file_name = “my_array.bin”
try:
with open(file_name, ‘wb’) as f: # ‘wb’ 模式用于写入二进制文件
my_array.tofile(f)
print(f”Array written to {file_name}”)

# fromfile(f, n): 从文件 f 读取 n 个元素到 array
read_array = array.array('i')
with open(file_name, 'rb') as f: # 'rb' 模式用于读取二进制文件
    # array.fromfile(f, len(my_array)) # 读取已知数量的元素
    # 或者读取文件中的所有剩余元素 (从 Python 3.5 开始)
    # 注意:对于 'i' 类型,每个元素是 itemsize 字节
    num_elements_to_read = os.path.getsize(file_name) // read_array.itemsize
    read_array.fromfile(f, num_elements_to_read)
print(f"Array read from {file_name}:", read_array)

finally:
# 清理文件
if os.path.exists(file_name):
os.remove(file_name)
print(f”Cleaned up {file_name}”)
“`

文件 I/O 功能是 array 模块的一个重要优势,特别适用于处理大量二进制数据文件,例如传感器数据、原始图像数据等,而无需将整个文件加载到内存中作为 Python 对象列表。

3.7 其他方法

array 模块还提供了一些其他方法:

  • byteswap(): 原地交换数组中所有元素的字节序。适用于处理不同字节序系统之间的数据交换。
  • buffer_info(): 返回一个包含数组当前缓冲区地址和元素个数的元组。这是一个低级接口,主要用于需要直接访问数组内存缓冲区的场景(如与其他语言交互)。
  • itemsize: 数组中每个元素占用的字节数。
  • typecode: 数组的类型代码字符。

“`python
import array
import sys # 用于检查系统字节序

my_array = array.array(‘i’, [1, 2, 3])
print(“Original array:”, my_array)

获取缓冲区信息

address, count = my_array.buffer_info()
print(f”Buffer Info: Address={address}, Count={count}”)

检查系统字节序

print(“System byte order:”, sys.byteorder)

byteswap() 示例 (如果需要交换的话)

注意:byteswap 会修改原数组

if sys.byteorder == ‘little’:
print(“Byteswapping array (assuming little-endian)…”)
my_array_swapped = array.array(my_array.typecode, my_array) # 创建一个副本进行操作
my_array_swapped.byteswap()
print(“Byteswapped array:”, my_array_swapped)
else:
print(“System is big-endian, byteswap would swap to little-endian.”)
my_array_swapped = array.array(my_array.typecode, my_array)
my_array_swapped.byteswap()
print(“Byteswapped array:”, my_array_swapped) # 交换后的结果在big-endian系统下是little-endian表示

print(“Original array after byteswap example:”, my_array) # 原数组未被副本的byteswap修改

“`

4. list vs array.array:何时选择哪个?

通过上面的介绍,我们可以清楚地看到 listarray.array 之间的主要区别:

特性 list array.array
元素类型 可以存储不同类型的元素 (异质) 只能存储同一种基本类型元素 (同质)
大小 动态可变 动态可变
内存使用 每个元素有额外的引用和类型开销,通常更多 元素紧密存储,内存效率更高
性能 通用性能良好,但处理大量数值数据可能不如同质数组快 对同质数值数据访问和操作可能更快,尤其结合文件 I/O
功能 功能丰富,通用性强 功能相对基础,侧重数值序列操作和二进制 I/O
创建方式 [], list(), [x for x in iterable] array.array(typecode, initializer)
典型用例 通用数据集合,混合类型数据,频繁增删 大量同类型数值数据,需要内存效率或直接进行二进制文件 I/O

何时使用 list:

  • 当你需要存储不同类型的数据时。
  • 当你需要频繁地在序列的任何位置插入或删除元素时(尽管在开头或中间插入/删除效率较低)。
  • 当你处理的数据量不是特别巨大,或者内存和极致性能不是首要考虑因素时。
  • 作为大多数通用编程任务的首选序列类型,因为它最灵活易用。

何时使用 array.array:

  • 当你需要存储大量同类型(特别是数值类型)的数据时。
  • 当内存效率是一个重要考量因素时,因为它比 list 更紧凑。
  • 当你需要直接与二进制文件交互,读写原始机器值时。
  • 当你想获得比 list 更好的性能(尽管对于大多数操作,尤其是在 Python 层面循环时,差距可能不如预期,真正需要极致数值性能通常会转向 NumPy)。

5. NumPy 数组(简要提及)

在 Python 的科学计算生态系统中,NumPy 是处理数值数据的事实标准。NumPy 提供的 ndarray 对象是一种多维度的同质数组,它在底层使用 C 或 Fortran 实现,提供了大量的优化数学函数和操作。

NumPy 数组的关键特点:

  • 多维度 (Multi-dimensional):支持创建任意维度的数组(向量、矩阵、张量等)。
  • 同质性 (Homogeneity):元素必须是同一数据类型,NumPy 支持更广泛的数值类型(如 int8, float64 等)。
  • 高性能 (High Performance):通过向量化操作避免 Python 层的循环,利用底层优化库(如 BLAS, LAPACK)实现高性能计算。
  • 丰富的功能 (Rich Functionality):提供广泛的数学、统计、线性代数、傅里叶变换等函数。

示例 (非常基础):

“`python
import numpy as np

创建一个 NumPy 数组

numpy_array = np.array([1, 2, 3, 4, 5], dtype=’int32′)
print(“NumPy Array:”, numpy_array)
print(“Data Type:”, numpy_array.dtype)
print(“Shape:”, numpy_array.shape)

多维数组

matrix = np.array([[1, 2], [3, 4]])
print(“Matrix:\n”, matrix)
print(“Shape:”, matrix.shape)

NumPy 数组支持向量化操作

result = numpy_array * 2 + 10
print(“Vectorized operation:”, result)

NumPy 数组与 array.array 的比较

创建一个 array.array

py_array = array.array(‘i’, [1, 2, 3, 4, 5])
print(“array.array:”, py_array)

注意:NumPy 数组和 array.array 不是完全互操作的,但可以相互转换

NumPy 数组可以轻松地从 array.array 创建

numpy_from_py = np.array(py_array)
print(“NumPy from array.array:”, numpy_from_py)
“`

对于任何涉及大量数值计算、矩阵运算、统计分析等任务,NumPy 数组几乎总是比 listarray.array 更优的选择,因为它在性能、功能和易用性(对于数值任务而言)方面具有压倒性优势。

总结 NumPy 的定位: 当你需要进行任何形式的数值计算或科学计算时,优先考虑 NumPy。array.array 则是在不引入外部库的情况下,处理大量同类型基本数据、特别是需要高效二进制文件 I/O 时的标准库选项。list 则是 Python 中最通用、最灵活的序列类型,适用于绝大多数非数值密集型或异质数据存储场景。

6. 总结

本文详细介绍了 Python 中与“数组”概念相关的几种数据结构:

  1. 传统的数组概念:同质、连续存储、固定大小、高效随机访问、内存效率高。
  2. Python 的 list:最常用的序列类型,灵活、动态大小、异质,但内存开销相对较大,非严格连续存储。它常被用作通用数组的替代。
  3. Python 标准库的 array 模块:提供了一种更接近传统数组的数据结构 (array.array),它是同质的、元素类型受限的、内存效率更高的序列,尤其适用于处理大量基本类型的数值数据和二进制文件 I/O。
  4. NumPy 数组 (ndarray):科学计算领域的事实标准,提供多维度、高性能、功能丰富的同质数组,适用于复杂的数值计算任务。

掌握这几种数据结构的特点和适用场景,是编写高效、健壮 Python 代码的关键。对于初学者来说,理解 list 的通用性,认识到 array.array 在特定场景(内存效率、二进制 I/O)的价值,并知道 NumPy 在数值计算领域的统治地位,将有助于你在面对不同的编程问题时,选择最合适的数据结构。

通过本文对 array.array 的详细介绍,包括创建、访问、修改、添加、删除、信息获取、转换以及文件操作等基础知识,你应该已经对这个模块有了深入的了解,并能够在需要时有效地利用它。记住,选择哪种“数组”取决于你的具体需求:数据类型、数据量、性能要求、是否需要多维度、是否需要进行复杂的数值运算等。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部