Python基础指南之二维列表(列表嵌套)的操作技巧
一、开篇:一层列表不够用的时候
前面几篇文章我们把一维列表(简单列表)的各种操作学了个遍。但在实际开发中,你遇到的数据结构往往不止一层。电子表格是二维的(行×列),游戏棋盘是二维的,图片像素也是二维的——这些场景都需要嵌套列表。
嵌套列表(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基础指南之二维列表(列表嵌套)的操作技巧的详细内容,更多关于Python二维列表操作的资料请关注脚本之家其它相关文章!


最新评论