你以为在遍历列表,其实你在“排座位”——嵌套列表的底层逻辑
你有没有过这样的时刻:写了一个 for 循环,自信满满地打印嵌套列表,结果输出了一堆 ['小明', '小红'] 这样的“半成品”?你明明写了循环,为什么拿不到里面的名字?
因为你只剥开了一层洋葱。嵌套列表,本质上就是一个“列表里装着列表”的结构,像教室里的座位表:外层是排,内层是每排的同学。要找到每一个具体的名字,必须两层循环——一层用来“找到哪一排”,另一层用来“找到这一排里的谁”。
先从座位表说起
假设教室有 3 排,每排 4 个同学:
seats = [
["小明", "小红", "小刚", "小美"],
["小华", "小丽", "小强", "小芳"],
["小军", "小智", "小文", "小杰"]
]
seats 的外层列表有三个元素,每个元素本身又是一个列表。如果你想打印所有名字,最直观的方式是:
for row in seats:
for name in row:
print(name, end=" ")
print()
外层的 row 是“一排人”,内层的 name 才是“具体的人”。少了任何一层,你都只能拿到“一排人”的整体,而不是里面的个体。
这听起来很简单,但真到了写代码的时候,很多人会犯一个典型的错误:只写一层循环,打印 row,然后疑惑“为什么输出的是列表?”——因为你手里拿到的是一整排的名册,而不是名册上的某个名字。
有时候你还想知道“第几排第几个”
单纯打印名字还不够。比如你要发作业本,得喊“第 2 排第 3 个同学,这是你的本子”。这时候需要同时知道索引和值,Python 给了我们 enumerate:
for i, row in enumerate(seats, start=1):
for j, name in enumerate(row, start=1):
print(f"第{i}排第{j}个是{name}")
start=1 是为了让排号和座位号从 1 开始,符合人类的直觉。如果你用默认的 0,就会输出“第 0 排第 0 个”,那会让班主任疯掉的。
这里有一个容易翻车的细节:内外层循环变量名不能相同。有人写:
for row in seats:
for row in row: # 内层 row 覆盖了外层 row
print(row)
内层循环一执行,row 就变成了最后一个名字,外层循环直接崩溃。变量名不仅仅是“名字”,它代表你当前正在操作的“那一层数据”。内层变量叫 name,外层叫 row,各司其职,互不干扰。
嵌套列表不只是座位表,还是矩阵
你以为嵌套列表只是用来存名字的?太小看它了。矩阵,就是嵌套列表的“学术形态”。
例如,让你计算两个矩阵的和。输入两个 n 行 m 列的矩阵 A 和 B,输出 A+B。考察的就是你能不能把两个嵌套列表的对应元素相加:
for i in range(n):
for j in range(m):
C[i][j] = A[i][j] + B[i][j]
外层循环控制行,内层循环控制列,A[i][j] 和 B[i][j] 就是同一位置上的两个数。这个逻辑,和“找到座位表里第 i 排第 j 个同学”一模一样。
矩阵乘法稍微复杂一点,但本质也没变:
for i in range(n):
for j in range(k):
for t in range(m):
C[i][j] += A[i][t] * B[t][j]
三层循环,每一层对应一个维度:i 是 A 的行,j 是 B 的列,t 是求和的下标。如果你能驾驭“排座位”的两层循环,那么三层循环只是“多套一层”而已,就像教室里有 3 个年级,每个年级 3 个班,每个班 4 排座位——你想找到某个同学,就得一层一层往下钻。
常见坑:少一层、变量混用、越界
我见过太多人栽在这三个坑里。
第一个坑:只写一层循环。比如有题目问,classroom = [["小明","小红"],["小刚","小丽"],["小华","小强"]],要依次输出每个名字。有人写:
for name in classroom:
print(name)
输出的是 ["小明", "小红"] 这种列表,而不是“小明”。因为 name 这个变量名误导了你——它实际指向的是子列表,而不是名字。变量叫什么不重要,重要的是它对应哪一层数据。
第二个坑:内外层变量名重复。前面已经说过了,这是新手最容易犯的错。
第三个坑:用下标时越界。比如有人用 range(len(matrix[i]) + 1),多了一个,直接 IndexError。如果你非要用下标,记住内层循环的范围是 len(matrix[i]),不是 len(matrix[i]) + 1,更不是某个拍脑袋的数。
一个判断:下面这段代码能正确打印吗?
matrix = [[1,2],[3,4],[5,6]]
for i in range(len(matrix)):
for j in range(len(matrix[i])):
print(matrix[i][j])
能。外层 i 遍历的是三个子列表的索引,内层 j 遍历的是每个子列表内部元素的索引。matrix[i][j] 正好访问到每一个数字。即使子列表长度不一样,len(matrix[i]) 也能保证不越界——因为每个子列表的长度都是它自己的。这正是安全遍历的关键:用当前子列表的实际长度来决定内层循环的范围。
从二维到任意维,你需要的只是“再多一层”
学完嵌套列表的遍历,你会发现自己能做的事情忽然变多了:课程表(星期×节次)、游戏地图(行×列)、成绩表(学生×科目)……本质上都是二维数据,都可以用两层循环搞定。
如果你再进一步,遇到三维数据(比如“每个年级每个班每个学生”),只需要再多套一层循环。循环的层数和嵌套的深度一一对应,这个规律不会变。
所以我给你的建议是:别怕嵌套。把它想象成“剥洋葱”——每剥一层,你就离核心更近一步。写代码的时候,先理清楚有几层结构,再决定写几个循环。一层结构用一个循环,两层结构用两个循环,永远不要试图用一个循环去硬闯两层数据,那样只会撞得满头包。
关于作者
我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。
这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。
如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)