云计算百科
云计算领域专业知识百科平台

Python基础:列表嵌套与二维列表操作技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

一、开篇:一层列表不够用的时候

前面几篇文章我们把一维列表(简单列表)的各种操作学了个遍。但在实际开发中,你遇到的数据结构往往不止一层。电子表格是二维的(行×列),游戏棋盘是二维的,图片像素也是二维的——这些场景都需要嵌套列表。

⌨️ 嵌套列表(Nested List)就是"列表里面套列表"。最常见的形态是二维列表——一个列表,其中每个元素又是一个列表,形成行和列的结构。但嵌套可以更深:三维列表用于体素游戏、四维列表用于科学计算……理解嵌套列表的创建、访问、修改和遍历,是处理结构化数据的基本功。

这篇文章我们会从二维列表的创建开始,讲到矩阵运算、棋盘游戏、数据透视表等实战应用,还会特别强调嵌套列表中那些让人掉坑的"引用共享"问题。

二、二维列表的创建方式

2.1 字面量创建(小型数据)

# 直接写出来的二维列表
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]

print(matrix[0]) # [1, 2, 3](第一行)
print(matrix[1][2]) # 6(第二行第三列)
print(len(matrix)) # 3(3行)
print(len(matrix[0])) # 3(第一行有3列)

2.2 用推导式创建(推荐方式)

# 方式一:推导式——创建规则矩阵
# 3行4列的全零矩阵
zeros = [[0 for _ in range(4)] for _ in range(3)]
print(zeros) # [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

# 生成乘法表
table = [[i * j for j in range(1, 6)] for i in range(1, 6)]
for row in table:
print(row)
# [1, 2, 3, 4, 5]
# [2, 4, 6, 8, 10]
# [3, 6, 9, 12, 15]
# [4, 8, 12, 16, 20]
# [5, 10, 15, 20, 25]

# 生成单位矩阵
n = 4
identity = [[1 if i == j else 0 for j in range(n)] for i in range(n)]
for row in identity:
print(row)
# [1, 0, 0, 0]
# [0, 1, 0, 0]
# [0, 0, 1, 0]
# [0, 0, 0, 1]

2.3 ⚠️ 经典陷阱:乘法创建二维列表

# 这是Python中最臭名昭著的陷阱之一!

# 错误写法——三行都指向同一个内部列表!
wrong = [[0] * 4] * 3
print(wrong) # [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
wrong[0][0] = 999
print(wrong) # [[999, 0, 0, 0], [999, 0, 0, 0], [999, 0, 0, 0]]
# 三行全部变了!因为三行是同一个列表的引用

# 验证三个"行"是否是同一个对象
print(wrong[0] is wrong[1]) # True
print(wrong[1] is wrong[2]) # True
print(id(wrong[0]), id(wrong[1]), id(wrong[2])) # 三个id相同!

# 正确写法——每一行都是独立创建的新列表
correct = [[0] * 4 for _ in range(3)]
correct[0][0] = 999
print(correct) # [[999, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
# 只有第一行变了!

print(correct[0] is correct[1]) # False(不同的对象)

💡 为什么 [[0]*4]*3 不行?因为 [[0]*4] 创建了一个内部列表 [0,0,0,0],然后 *3 把这个列表的引用复制了三份。三行指向内存中的同一个列表。永远用推导式创建嵌套列表,不要用乘法。

2.4 创建不规则二维列表

# 二维列表不一定是矩形的(锯齿状/不规则)
jagged = [
[1, 2, 3],
[4, 5],
[6, 7, 8, 9],
]

# 访问不同行的列数
for i, row in enumerate(jagged):
print(f'第{i}行有{len(row)}列: {row}')

# 锯齿列表在表示层次结构时很有用
# 例如:部门→员工
departments = [
['张总'],
['李经理', '王经理'],
['小赵', '小钱', '小孙', '小李'],
]

三、二维列表的访问与遍历

3.1 索引访问

matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
]

# 访问单个元素:[行][列]
print(matrix[0][0]) # 1(第一行第一列)
print(matrix[1][2]) # 7(第二行第三列)
print(matrix[2][3]) # 12(第三行第四列)
print(matrix[–1][–1]) # 12(最后一行最后一列)

# 访问整行
print(matrix[0]) # [1, 2, 3, 4]

# 访问整列(用推导式)
col_0 = [row[0] for row in matrix]
print(col_0) # [1, 5, 9]

col_2 = [row[2] for row in matrix]
print(col_2) # [3, 7, 11]

3.2 遍历方式

matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]

# 方式一:逐行遍历(最简单)
for row in matrix:
print(row)

# 方式二:行列双重循环
for i in range(len(matrix)): # 遍历行
for j in range(len(matrix[i])): # 遍历列
print(f'matrix[{i}][{j}] = {matrix[i][j]}')

# 方式三:enumerate双重循环(推荐,最Pythonic)
for i, row in enumerate(matrix):
for j, value in enumerate(row):
print(f'matrix[{i}][{j}] = {value}')

# 方式四:逐个元素展开遍历
for value in [cell for row in matrix for cell in row]:
print(value, end=' ') # 1 2 3 4 5 6 7 8 9

3.3 行列操作

matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]

# 获取对角线元素
main_diag = [matrix[i][i] for i in range(len(matrix))]
print(f'主对角线: {main_diag}') # [1, 5, 9]

anti_diag = [matrix[i][len(matrix) – 1 – i] for i in range(len(matrix))]
print(f'反对角线: {anti_diag}') # [3, 5, 7]

# 获取某一行或某一列的切片
# 取第2行(索引1)的前2个元素
print(matrix[1][:2]) # [4, 5]

# 取第1列的所有元素
col_0 = [row[0] for row in matrix]
print(col_0) # [1, 4, 7]

# 获取子矩阵
def submatrix(matrix, row_start, row_end, col_start, col_end):
"""提取子矩阵"""
return [row[col_start:col_end] for row in matrix[row_start:row_end]]

print(submatrix(matrix, 0, 2, 1, 3)) # [[2, 3], [5, 6]]

四、二维列表的修改

4.1 修改单个元素和整行

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# 修改单个元素
matrix[1][2] = 99
print(matrix[1]) # [4, 5, 99]

# 修改整行
matrix[0] = [10, 20, 30]
print(matrix) # [[10, 20, 30], [4, 5, 99], [7, 8, 9]]

# 修改整列(需要通过循环)
for row in matrix:
row[1] = 0 # 将第2列全部设为0
print(matrix) # [[10, 0, 30], [4, 0, 99], [7, 0, 9]]

4.2 增加和删除行列

matrix = [[1, 2, 3], [4, 5, 6]]

# 增加一行
matrix.append([7, 8, 9])
print(matrix) # [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# 增加一列(给每行追加一个元素)
for i, row in enumerate(matrix):
row.append((i + 1) * 10)
print(matrix) # [[1, 2, 3, 10], [4, 5, 6, 20], [7, 8, 9, 30]]

# 删除一行
del matrix[1]
print(matrix) # [[1, 2, 3, 10], [7, 8, 9, 30]]

# 删除一列
for row in matrix:
del row[–1] # 删除每行的最后一个元素
print(matrix) # [[1, 2, 3], [7, 8, 9]]

# 插入一行
matrix.insert(1, [100, 200, 300])
print(matrix) # [[1, 2, 3], [100, 200, 300], [7, 8, 9]]

五、矩阵运算实战

5.1 矩阵转置

def transpose(matrix):
"""转置矩阵——行列互换"""
if not matrix:
return []
rows, cols = len(matrix), len(matrix[0])
# 创建 cols × rows 的新矩阵
return [[matrix[i][j] for i in range(rows)] for j in range(cols)]

matrix = [
[1, 2, 3],
[4, 5, 6],
]
transposed = transpose(matrix)
print('原矩阵:')
for row in matrix:
print(row)
print('转置:')
for row in transposed:
print(row)
# [1, 4]
# [2, 5]
# [3, 6]

# 更简单的方式:用zip
transposed2 = [list(col) for col in zip(*matrix)]
print(transposed2) # [[1, 4], [2, 5], [3, 6]]

5.2 矩阵加法与数乘

def matrix_add(a, b):
"""矩阵加法:对应位置相加"""
rows, cols = len(a), len(a[0])
return [[a[i][j] + b[i][j] for j in range(cols)] for i in range(rows)]

def matrix_scalar_mul(matrix, scalar):
"""矩阵数乘:每个元素乘以标量"""
return [[cell * scalar for cell in row] for row in matrix]

a = [[1, 2, 3], [4, 5, 6]]
b = [[7, 8, 9], [1, 2, 3]]

print('矩阵加法:')
for row in matrix_add(a, b):
print(row)
# [8, 10, 12]
# [5, 7, 9]

print('矩阵数乘:')
for row in matrix_scalar_mul(a, 2):
print(row)
# [2, 4, 6]
# [8, 10, 12]

5.3 矩阵乘法

def matrix_multiply(a, b):
"""矩阵乘法:a的行数 × b的列数"""
if not a or not b:
return []
if len(a[0]) != len(b):
raise ValueError('矩阵尺寸不匹配')

rows_a, cols_a = len(a), len(a[0])
cols_b = len(b[0])

result = [[0] * cols_b for _ in range(rows_a)]
for i in range(rows_a):
for j in range(cols_b):
result[i][j] = sum(a[i][k] * b[k][j] for k in range(cols_a))
return result

a = [[1, 2, 3], [4, 5, 6]] # 2×3
b = [[7, 8], [9, 10], [11, 12]] # 3×2

c = matrix_multiply(a, b)
print('矩阵乘法结果 (2×2):')
for row in c:
print(row)
# [58, 64]
# [139, 154]

# 验证:c[0][0] = 1*7 + 2*9 + 3*11 = 7 + 18 + 33 = 58 ✓

六、棋盘游戏——二维列表的经典应用

6.1 井字棋(Tic-Tac-Toe)

class TicTacToe:
def __init__(self):
# 3×3的棋盘
self.board = [[' ' for _ in range(3)] for _ in range(3)]
self.current_player = 'X'

def display(self):
"""显示棋盘"""
print(' 0 1 2')
for i, row in enumerate(self.board):
print(f'{i} ' + ' | '.join(row))
if i < 2:
print(' —+—+—')

def make_move(self, row, col):
"""落子"""
if self.board[row][col] != ' ':
return False
self.board[row][col] = self.current_player
self.current_player = 'O' if self.current_player == 'X' else 'X'
return True

def check_winner(self):
"""检查是否有赢家"""
b = self.board
# 检查行
for row in b:
if row[0] == row[1] == row[2] != ' ':
return row[0]
# 检查列
for col in range(3):
if b[0][col] == b[1][col] == b[2][col] != ' ':
return b[0][col]
# 检查对角线
if b[0][0] == b[1][1] == b[2][2] != ' ':
return b[0][0]
if b[0][2] == b[1][1] == b[2][0] != ' ':
return b[0][2]
# 检查是否平局
if all(cell != ' ' for row in b for cell in row):
return '平局'
return None

def get_empty_cells(self):
"""获取所有空单元格的位置"""
return [(i, j) for i in range(3) for j in range(3)
if self.board[i][j] == ' ']

6.2 扫雷——生成棋盘

import random

def create_minesweeper(rows, cols, mines):
"""创建扫雷棋盘"""
# 创建空棋盘
board = [[0 for _ in range(cols)] for _ in range(rows)]

# 随机放置地雷
mine_positions = set()
while len(mine_positions) < mines:
r = random.randint(0, rows – 1)
c = random.randint(0, cols – 1)
mine_positions.add((r, c))
board[r][c] = –1 # -1表示地雷

# 计算每个非雷格子的数字(周围8格的地雷数)
directions = [(–1,–1), (–1,0), (–1,1), (0,–1),
(0,1), (1,–1), (1,0), (1,1)]

for r, c in mine_positions:
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] != –1:
board[nr][nc] += 1

return board

board = create_minesweeper(8, 8, 10)
print('扫雷棋盘(-1=雷,数字=周围雷数):')
for row in board:
print(' '.join(f'{cell:2d}' for cell in row))

七、数据表格处理

7.1 CSV到二维列表

def parse_csv_to_matrix(text, delimiter=','):
"""将CSV文本解析为二维列表"""
lines = text.strip().split('\\n')
return [line.split(delimiter) for line in lines if line.strip()]

csv_text = '''
姓名,年龄,城市,职业
小明,25,北京,工程师
小红,23,上海,设计师
小刚,26,广州,分析师
'''

data = parse_csv_to_matrix(csv_text)
print('原始数据:')
for row in data:
print(row)

# 按列排序
def sort_by_column(data, col_index, numeric=False):
"""按指定列排序二维列表"""
headers = data[0]
body = data[1:]
if numeric:
body.sort(key=lambda row: float(row[col_index]))
else:
body.sort(key=lambda row: row[col_index])
return [headers] + body

sorted_data = sort_by_column(data, 1, numeric=True)
print('\\n按年龄排序:')
for row in sorted_data:
print(row)

7.2 数据透视

def pivot_table(data, row_field, col_field, value_field, agg_func=sum):
"""
用二维列表模拟数据透视表
data: 字典列表
"""

# 收集所有唯一的行和列值
row_values = sorted(set(d[row_field] for d in data))
col_values = sorted(set(d[col_field] for d in data))

# 创建透视表
pivot = [[0 for _ in range(len(col_values))] for _ in range(len(row_values))]

# 建立索引映射
row_idx = {v: i for i, v in enumerate(row_values)}
col_idx = {v: i for i, v in enumerate(col_values)}

# 按行列收集数据
groups = {}
for record in data:
key = (record[row_field], record[col_field])
if key not in groups:
groups[key] = []
groups[key].append(record[value_field])

# 填充透视表
for (r, c), values in groups.items():
pivot[row_idx[r]][col_idx[c]] = agg_func(values)

return pivot, row_values, col_values

sales_data = [
{'月份': '1月', '产品': 'A', '销售额': 100},
{'月份': '1月', '产品': 'B', '销售额': 150},
{'月份': '1月', '产品': 'A', '销售额': 200},
{'月份': '2月', '产品': 'A', '销售额': 120},
{'月份': '2月', '产品': 'B', '销售额': 180},
{'月份': '3月', '产品': 'A', '销售额': 90},
{'月份': '3月', '产品': 'B', '销售额': 160},
]

pivot, rows, cols = pivot_table(sales_data, '月份', '产品', '销售额', sum)
print('数据透视表:')
print(f'{"":>6}', end='')
for c in cols:
print(f'{c:>8}', end='')
print()
for i, r in enumerate(rows):
print(f'{r:>6}', end='')
for j in range(len(cols)):
print(f'{pivot[i][j]:>8}', end='')
print()

八、三维列表与更高维

8.1 三维列表基础

# 三维列表 = 多个二维平面的堆叠
# 例如:2个平面 × 3行 × 4列

cube = [
[ # 平面0
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
],
[ # 平面1
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
],
]

# 访问:[平面][行][列]
print(cube[0][1][2]) # 7(平面0,行1,列2)
print(cube[1][2][3]) # 24(平面1,行2,列3)

# 遍历三维列表
for p, plane in enumerate(cube):
print(f'平面 {p}:')
for row in plane:
print(f' {row}')

# 三维列表应用:RGB图像
# shape: [通道][行][列] → [3][height][width]
# 通道0=R, 通道1=G, 通道2=B

8.2 创建三维列表(避免引用陷阱)

# 错误做法(引用共享!)
# cube = [[[0]*3]*4]*2 # 平面间、行间都会共享引用!

# 正确做法:嵌套推导式
depth, rows, cols = 2, 3, 4
cube = [[[0 for _ in range(cols)] for _ in range(rows)] for _ in range(depth)]

cube[0][1][2] = 99
print('平面0修改后:')
for plane in cube:
for row in plane:
print(row)
print()
# 只有平面0的行1列2变成99,其他不受影响

九、嵌套列表的深拷贝与浅拷贝

9.1 浅拷贝的问题

import copy

original = [[1, 2], [3, 4]]

# 浅拷贝——只复制外层列表
shallow = original[:]
# shallow = original.copy()
# shallow = list(original)
# shallow = copy.copy(original)

# 修改外层——不影响原列表
shallow.append([5, 6])
print(original) # [[1, 2], [3, 4]](不变)

# 修改内层——影响原列表!
shallow[0][0] = 999
print(original) # [[999, 2], [3, 4]](变了!)
print(shallow) # [[999, 2], [3, 4], [5, 6]]

9.2 深拷贝解决

original = [[1, 2], [3, 4]]

# 深拷贝——递归复制所有层级
deep = copy.deepcopy(original)

# 修改内层——不影响原列表
deep[0][0] = 999
print(original) # [[1, 2], [3, 4]](完全不变)
print(deep) # [[999, 2], [3, 4]]

# 手动深拷贝二维列表(如果不想导入copy模块)
def manual_deep_copy_2d(matrix):
"""手动深拷贝二维列表"""
return [row[:] for row in matrix]

# 但这只适用于二维!三维以上还是用copy.deepcopy

十、本篇小结

✅ 嵌套列表(特别是二维列表)是处理结构化数据的基础:

  • 创建:永远用推导式创建嵌套列表,不用乘法([[0]*n]*m会共享内层引用)
  • 访问:matrix[行][列],支持负索引和切片
  • 修改:修改单个元素直接索引赋值,修改整列需要循环
  • 遍历:enumerate双重循环是最Pythonic的方式
  • 矩阵运算:转置(zip或推导式)、加法、乘法是数据科学的基础操作
  • 深拷贝:嵌套列表复制时注意浅拷贝只复制外层,内层对象共享
  • 📝 嵌套列表是连接"简单数据结构"和"实际应用"的桥梁。从井字棋到数据透视表,从扫雷到图像处理——二维列表的应用无处不在。理解并避开引用共享的陷阱,是掌握嵌套列表的关键。下一篇我们将离开列表,进入Python另一个重要的内置序列——元组tuple的不可变特性深度解析。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Python基础:列表嵌套与二维列表操作技巧
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!