简介:本文面向刚接触Python的开发者,系统讲解多维列表的创建方式与常见陷阱。你将了解列表推导式、嵌套循环、动态扩容等实用方法,掌握深浅拷贝的区别,避免引用共享导致的隐蔽错误,并学会用NumPy替代方案处理大规模数值数据,从而写出更健壮的代码。
很多从C/C++转过来的开发者会怀念int a[3][4][5]这种声明方式:维度、大小一次说清,内存布局固定。而Python的列表是动态的、可变的、元素可以是任意对象,它并没有语言级别的”多维数组”语法糖。于是新手常写出这样的代码:
matrix = [[0] * 3] * 3matrix[0][0] = 1print(matrix)# 输出: [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
本意是修改左上角一个元素,结果三行全变了。原因在于[[0] * 3] * 3只是把同一个内层列表的引用复制了三份,三个”行”指向同一块内存。理解这一点,是掌握多维列表创建的关键。
下面按从简到繁的顺序,介绍几种常见且正确的创建方式。
列表推导式是创建多维列表最地道、最不易出错的方式,因为每次循环都会新建一个内层列表:
rows, cols = 3, 4matrix = [[0 for _ in range(cols)] for _ in range(rows)]matrix[0][0] = 1print(matrix)# 输出: [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
三层同理:
cube = [[[0 for _ in range(5)] for _ in range(4)] for _ in range(3)]
要点:内层表达式必须产生新对象。写成[[0] * cols for _ in range(rows)]也是安全的,因为外层推导式每轮都执行一次[0] * cols,得到新的内层列表;危险的是把* rows放在最外层。
当初始化逻辑较复杂(例如每个元素需要计算)时,显式循环更清晰:
rows, cols = 3, 4matrix = []for i in range(rows):row = []for j in range(cols):row.append(i * cols + j)matrix.append(row)
这种写法牺牲了一点简洁性,换来的是逻辑一目了然,调试也方便。
Python的”多维列表”本质是”列表的列表”,因此各行列数可以不同,这在表示邻接表、变长分组时非常有用:
triangle = [[0] * (i + 1) for i in range(5)]# [[0], [0, 0], [0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0]]
这恰恰是C的定长数组做不到的,是Python动态结构的一大优势,只是在需要矩形矩阵时要自行保证各行列数一致。
实际业务中,维度往往在运行期才确定,常见做法是先建空结构再逐层追加:
matrix = []for line in data_source(): # 假设逐行读取row = [parse(item) for item in line]matrix.append(row)
或者先按需创建占位行,再填充:
matrix = [[] for _ in range(rows)]for i, value in enumerate(values):matrix[i % rows].append(value)
当维度很大但有效数据很少时,用嵌套列表会浪费大量内存。可以用”坐标元组 → 值”的字典表示:
sparse = {}sparse[(2, 5, 1)] = 42print(sparse.get((2, 5, 1), 0))
这是处理高维稀疏数据的常用技巧,思路与通用的稀疏表示一致。
创建多维列表的另一大陷阱出现在”复制”环节:
a = [[1, 2], [3, 4]]b = a[:] # 浅拷贝:内层列表仍是共享的b[0][0] = 99print(a) # [[99, 2], [3, 4]],a 被意外修改
正确的深拷贝方式:
import copyb = copy.deepcopy(a)
或者针对二维结构手写:
b = [row[:] for row in a]
判断标准很简单:只要需要独立修改内层元素,就必须逐层复制。这也是很多”数据莫名其妙被改”问题的根源。
创建之后,遍历方式也值得梳理:
matrix = [[1, 2, 3], [4, 5, 6]]# 按行遍历for row in matrix:for value in row:print(value)# 按下标遍历(需要修改元素时)for i in range(len(matrix)):for j in range(len(matrix[i])):matrix[i][j] *= 2# 转置(借助 zip)transposed = [list(row) for row in zip(*matrix)]
注意len(matrix)只给出行数,列数需看具体行;对锯齿状结构遍历时,内层循环上界应写成len(matrix[i]),否则容易越界。
纯Python的嵌套列表在元素访问上有解释器开销,且每个元素都是独立的对象引用,内存占用较高。当数据规模上升、且元素类型统一为数值时,应考虑使用数组化的数值计算库(如业界广泛使用的多维数组库):
示例(示意性写法):
import numpy as nparr = np.zeros((3, 4), dtype=np.int32)arr[0, 0] = 1
需要注意,数组化结构通常要求各维长度一致(规则张量),且元素类型统一;若数据结构本身不规则,或需要存放异构对象,嵌套列表仍是更灵活的选择。选型时可以按以下思路判断:
[[x] * n] * m这类共享引用的写法。copy.deepcopy。rows/cols等命名,避免行列颠倒;对锯齿结构遍历时用len(row)而非固定列数。len(matrix), len(matrix[0]))确认符合预期。Python没有C那样的多维数组声明语法,但它用”列表的列表”提供了更灵活的表达能力。只要抓住两条核心原则——创建时保证每层都是新对象,复制时按需深拷贝——就能避开绝大多数坑。在此基础上,根据数据规模与结构特点在嵌套列表与数值数组库之间做出合理选择,便能兼顾开发效率与运行性能。