资讯详情

Python循环嵌套从入门到实战:逻辑拆解、性能优化与避坑指南

📅 2026/10/6 4:21:08 | 华诺云谱 👁 阅读
Python循环嵌套从入门到实战:逻辑拆解、性能优化与避坑指南
作为一个教了多年Python、自己也从“看到循环就头疼”阶段爬过来的人我太知道“循环嵌套”这四个字对新手意味着什么了。很多人学完 for 和 while单层循环已经能跑通可一旦看到两层循环叠在一起大脑立刻宕机不知道哪一层先执行、哪一层后执行更不知道什么场景该用嵌套。这篇进阶指南不打算堆概念就围绕“循环嵌套”从逻辑拆解、实战案例、性能优化到避坑排查一步步带你把脑海里那团乱麻理顺。内容适合刚学完Python基础语法、正在刷循环题或者准备用嵌套做小项目的朋友也适合那些代码能跑但说不清原理的人。1. 循环嵌套到底在干什么先别写代码把逻辑画明白1.1 嵌套的本质不是“循环套循环”而是“外层跑一遍内层跑全套”很多新手理解嵌套会把注意力放在“套”这个字上以为就像盒子套盒子一层层往里钻。这个理解没错但太抽象。我更喜欢用另一个说法外层循环每执行一次内层循环就要完完整整地从头跑到尾。这不是比喻而是嵌套运行的唯一法则。你可以想象一个钟表。时针每走一格分针要足足转一圈分针每走一格秒针又要转一圈。如果把时针、分针、秒针都看作循环变量那这就是一个三层嵌套最外层跑得最慢最内层跑得最快。写代码的时候Python解释器会从外往内执行一旦进入外层循环体它就一头扎进内层循环非要把内层所有迭代全部跑完才会回到外层继续下一轮。理解了这个“跑一遍还是跑全套”的区别嵌套就入门了一半。用生活里的例子还有一个好处它直接解释了为什么嵌套常用来处理“二维结构”。比如一张课程表行是星期几列是第几节课一个班级成绩表行是学生列是科目。但凡数据本身有行有列想逐个访问就自然需要一个变量管行、一个变量管列这两个变量各自需要一条循环线合起来就是嵌套。所以当你看到两层循环时第一反应不应该是“代码好复杂”而应该是“这里处理的是二维数据或者是一个需要两两组合的遍历场景”。有了这个判断后面写代码的思路就清楚了。1.2 用两个小例子建立直觉九九乘法表和矩阵遍历我教学生时第一个嵌套例子永远是九九乘法表因为它内置了两层循环的经典依赖关系——内层循环的上限依赖外层循环的当前值。for i in range(1, 10): # 外层控制行 for j in range(1, i 1): # 内层控制列上限是 i print(f{j} x {i} {i * j}, end\t) print() # 每行结束换行跑这段代码你会看到第一行只有 1 个式子第二行 2 个直到第九行 9 个。为什么因为外层 i 从 1 变到 9内层 j 的 range 上限是 i1也就是说外层 i 的值直接决定了内层要循环多少次。这就是依赖型嵌套内层范围随外层变化。再看矩阵遍历。假设有一个 3 行 4 列的二维列表matrix [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] ] for row in range(len(matrix)): # 外层第几行 for col in range(len(matrix[row])): # 内层这一行第几列 print(matrix[row][col], end ) print()输出会是完整的矩阵。这里外层变量 row 管行号内层变量 col 管列号两层之间没有大小依赖只是组合遍历。两个例子对照着看一个依赖、一个独立你会发现“嵌套”这个词本身并不重要重要的是搞清楚两层循环之间的变量关系。2. 从“看得懂”到“写得对”五步拆解法把嵌套题变成填空题2.1 第一步确定外层循环要枚举什么东西拿到一道嵌套题先别急着敲代码问自己一个问题这个问题里有哪些东西是“一组一组”的如果有两组哪一组的变化更慢、更外层举一个简单的例子打印一个 5 行 5 列的星号方阵。行有 5 个列有 5 个行和列地位相同。但如果我们把“打印每一行”看作一个整体那么外层循环就该负责“换行”内层循环负责“在一行里连续打印 5 个星号”。这时候外层循环枚举的是行号内层枚举的是列号。再比如你想输出所有两位数10 到 99的十位和个位组合。十位从 1 到 9个位从 0 到 9十位显然应该放在外层因为它变化更慢。确定外层枚举什么本质上是确定“谁主导谁”。一般来说主导维度放在外层从属维度放在内层。2.2 第二步确定内层循环依赖不依赖外层这是嵌套里最重要的分水岭。如果内层循环的 range 上限是一个固定数字不随外层变量改变那这就是独立型嵌套两层循环的迭代次数相乘总共执行外层次数乘以内层次数。比如打印一个完整的 4×6 矩形内层次数永远是 6就属于这类。如果内层循环的 range 上限使用了外层变量比如range(1, i1)那就是依赖型嵌套。这种嵌套最常见的用途是打印三角形、处理矩阵的上三角或下三角元素。判断依赖关系的方法很简单把外层变量随便代一个数进去看看内层 range 会不会变。会变就是依赖型。新手最容易在这里出问题因为他们总以为嵌套的写法是固定的。其实嵌套只是一个框架内层循环完全可以“看外层脸色行事”。搞清楚了依赖关系你就知道该把哪个变量写进内层 range 里。2.3 第三步找边界写 range 别拍脑袋写嵌套最容易出错的是边界。range 的规则是“含头不含尾”也就是range(start, stop)会生成从 start 到 stop-1 的整数。很多人写成range(1, n)以为会包含 n结果少了一行或一列。关于边界我给一个可复用的思考方式先把问题里的最小情况和最大情况想清楚。比如打印直角三角形行数是 n第 i 行要打印 i 个星号。那么第 1 行打印 1 个第 n 行打印 n 个。如果使用for j in range(i)当 i1 时打印 1 个当 in 时打印 n 个完全正确。这里 i 从 1 开始所以range(i)生成 0 到 i-1 共 i 个值刚好。如果你要从 0 开始计数比如矩阵的行号和列号通常从 0 开始那就需要小心“第 0 行”的边界。比如打印一个 n 行倒三角第 0 行打印 n 个第 n-1 行打印 1 个内层可以用range(n - i)i 从 0 开始。这类边界题做多了自然熟练但一开始一定要用具体数字代入验证。2.4 第四步先写内层再套外层或者反过来新手写嵌套时最大的心理障碍是“一次写两层”。我推荐的写法是先假装只有一层把内层循环单独写出来跑通后再在外面套上一层。例如你想打印一个 5 行的等腰三角形。先不管行数假设当前是第 i 行这一行需要先打印若干个空格再打印若干个星号。那么“打印空格”和“打印星号”分别是两个内层循环。你先单独把其中一个循环写出来比如for j in range(5): print(*, end)确认它能打印 5 个星号。然后再加上空格循环for j in range(3): print( , end) for k in range(5): print(*, end)最后在外面套一个行循环并把空格数和星号数改成由行号 i 计算出来的表达式。这个过程把一个大问题拆成了几个小问题每一步都可独立验证出错概率大大降低。2.5 第五步输出调试代换法就算你逻辑想清楚了写出来的嵌套还是可能有偏差。这时候别盯着代码愣神用一个笨但极其有效的方法在循环体里临时加一行 print把外层变量和内层变量的当前值打出来。for i in range(3): for j in range(2): print(f外层 i{i}, 内层 j{j})运行后你会看到 i 从 0 到 2每个 i 下 j 都从 0 到 1。这就是最直观的“运行轨迹”。如果发现某个组合没出现或者不该出现的出现了那就说明 range 边界写错了。调试完记得把 print 删掉或者注释掉。这个“代换法”再进阶一点就是手动模拟拿第一次循环代入变量走一遍再拿最后一次循环代一遍。两次验证就能把大多数边界问题揪出来。嵌套本身不难难的是你脑子里没有一张“变量怎么变”的动态图print 就是帮你画这张图的工具。3. 实战案例用循环嵌套做点有实际价值的事3.1 案例一用嵌套统计学生成绩矩阵假设你手上有一个成绩单每一行是一个学生的语数英三科成绩你想快速算出每个学生的平均分、每科的最高分。scores [ [88, 76, 92], [95, 83, 67], [70, 91, 85], [82, 78, 90] ] # 每个学生的平均分 for i in range(len(scores)): total 0 for j in range(len(scores[i])): total scores[i][j] avg total / len(scores[i]) print(f学生{i1}的平均分: {avg:.1f}) # 每科的最高分 subject_num len(scores[0]) for j in range(subject_num): max_score 0 for i in range(len(scores)): if scores[i][j] max_score: max_score scores[i][j] print(f第{j1}科的最高分: {max_score})注意看这两段嵌套的差异。计算学生平均分时外层遍历学生内层遍历科目这时每一行的 sum 变量在外层循环内初始化确保每个学生重新从 0 加起。而计算科目最高分时外层反而变成了科目内层遍历学生因为“某一科的最高分”需要跨所有学生比较。这个小案例告诉你嵌套的内外层顺序不是拍脑袋定的而是由你要输出的结果结构决定的。你要按行汇总就把行放外层你要按列汇总就把列放外层。理解了这个以后再遇到“转置矩阵”“行列求和”之类的题就能自己推导出循环顺序了。3.2 案例二生成坐标网格为后续算法打基础做游戏、画图表、写网格搜索都逃不过生成坐标点。嵌套循环是生成笛卡尔积最朴素的方式。width, height 4, 3 points [] for x in range(width): for y in range(height): points.append((x, y)) print(points)输出是(0,0), (0,1), (0,2), (1,0) ... (3,2)一共 12 个点。这里 x 和 y 的组合就是两层循环的笛卡尔积。为什么先 x 后 y因为外层 x 变化慢得到的坐标序列是先竖着扫完一列再换下一列。如果你希望先横着扫完一行就把 y 放外层。这种坐标网格在很多场景里都能用。比如你想在图片上每隔 10 像素取一个采样点或者想遍历棋盘上的所有格子本质上都是嵌套循环。也包括用 Python 构建邻接矩阵时经常需要两层循环来填充矩阵的每个元素外层管行内层管列坐标网格的思想一脉相承。3.3 案例三实现一个简单的冒泡排序看嵌套如何控制比较范围排序对新手来说有点吓人但冒泡排序是一个特别适合理解嵌套“依赖关系”的例子。nums [5, 2, 9, 1, 7] for i in range(len(nums) - 1): # 外层总共需要跑几轮 for j in range(len(nums) - 1 - i): # 内层每轮比较几次 if nums[j] nums[j 1]: nums[j], nums[j 1] nums[j 1], nums[j] print(nums)第一次外层 i0内层 j 从 0 到 3比较 4 次把最大的数 9 冒到末尾。第二次 i1内层 j 从 0 到 2比较 3 次因为最后一个位置已经定好了不用再碰。随着 i 增大内层范围不断缩小这就是典型的依赖型嵌套。这个案例的深刻之处在于它展示了嵌套的层数不一定意味着“复杂”而是“重复执行某种操作且该操作的范围在动态变化”。你看着代码里只有一个 if 交换但就是因为外层控制了内层范围的收缩整个数组才得以排序。理解冒泡排序的嵌套你就理解了几乎所有“多轮扫描”类算法的骨架。4. 性能与优化嵌套循环为什么慢怎么救4.1 复杂度估算O(n*m)是怎么来的平方级陷阱嵌套循环的性能杀手在于“乘法”。两层循环总共要执行外层次数乘以内层次数次所以如果两层都是 n总次数就是 n 的平方。当 n10 时是 100 次n1000 时是 100 万次n10000 时是 1 亿次。这个增长速度会迅速让你的代码变慢。我见过一个真实的例子有人用双层循环处理一个 5000×5000 的矩阵两层都是 5000总共要跑 2500 万次虽然也能跑完但明显卡顿。如果你在循环体里还有字符串拼接、文件读写这种重操作那就不只是卡顿而是根本上不可用。所以看到嵌套循环第一反应永远是估一下总共会执行多少次。只要这个次数在百万级以下纯 Python 一般能扛到了千万级就要考虑优化上亿级别基本必须换思路或者借助 NumPy 这类库做向量化计算。4.2 常见优化思路提前跳出、剪枝、用推导式替代部分嵌套优化不是让你把嵌套删了而是让你少做无用功。第一个套路是提前跳出。比如你要在二维列表里查找某个值是否存在一旦找到就可以立刻结束没有必要把剩下所有元素都遍历一遍。这时候可以用 break 跳出内层循环再配合一个标志位或者直接 return如果在函数里跳出外层。第二个套路是剪枝特别适合依赖型嵌套。比如遍历矩阵的上三角区域你完全可以让内层循环的起点跟外层变量挂钩从而跳过矩阵左下角那一半不需要处理的数据for i in range(n): for j in range(i, n): # 只处理列号 行号的部分 # do something这样总次数从 n 的平方变成了 n(n1)/2几乎少了一半的工作量。别小看这个改动在处理大矩阵时收益非常明显。第三个套路是用列表推导式替代简单的两层循环。比如生成所有坐标点与其写三行嵌套再用 append不如一行搞定points [(x, y) for x in range(4) for y in range(3)]推导式的执行速度通常比普通循环快而且代码更简洁。不过要注意如果循环体里有复杂的条件分支或者需要修改多个数据结构就不要强行用推导式可读性先于炫技。4.3 什么时候该放弃嵌套改用枚举组合、矩阵库嵌套并不是万能的。有些场景明明要处理“组合关系”你却用两层甚至三层循环硬算这时就应该考虑换工具了。比如你想从一个列表里选两个不重复的元素组成组合朴素写法是两层循环然后通过判断 i ! j 来跳过重复项。但如果列表有 1000 个元素你就会多算差不多一半的重复配对。更好的办法是用 Python 标准库itertools里的combinations它专门干这个事底层是高效的 C 实现比你手写嵌套快得多。from itertools import combinations nums [1, 2, 3, 4] for a, b in combinations(nums, 2): print(a, b)再比如处理矩阵乘法、图像卷积这类数值密集操作纯 Python 嵌套循环慢到让人怀疑人生。正确姿势是使用 NumPy把循环交给底层优化过的矩阵运算。逻辑上你的确还在“遍历”但代码层面已经不需要自己写两层循环了。学会了判断“该不该用嵌套”你才算真正掌控了它而不是被它绑架。5. 新手最常踩的坑变量污染、缩进错位、死循环5.1 外层和内层的变量名互相覆盖新手喜欢把内层循环变量也起名叫 i跟外层一样。这在某些语言里可能没问题也有很多人觉得“反正后面不再用了重名就重名”。但在嵌套里如果你在内层循环体内还要访问外层 i 的值一旦重名内层的 i 就会把外层的 i 盖住代码逻辑直接崩。正确的习惯是外层用 row、i内层用 col、j变量名要有明确区分。如果嵌套层数更多还可以用 i、j、k 依次命名但最好每层代表有实际含义的名字比如 x、y、z 或者 student、subject。变量重名的坑一旦踩到调试起来非常痛苦因为错误不是直接报异常而是结果诡异。5.2 缩进错位让循环归属变味Python 用缩进表示代码块这是它优雅的地方也是新手最容易翻车的地方。同一个 print缩进不同含义完全不同。for i in range(3): for j in range(3): print(i, j) print(---)这个“---”在每个 i 结束之后打印一次总共 3 次。如果把它再往右缩进一格变成跟 print(i, j) 同级for i in range(3): for j in range(3): print(i, j) print(---)那就变成每个 j 都打印一次“---”总共 9 次。代码看起来差别不大输出天差地别。建议在写嵌套的时候同一层级的代码缩进严格对齐并且用编辑器自带的“显示缩进线”功能辅助检查。也可以把内层循环体单独抽成一个函数这样缩进层级浅了代码也更好读。5.3 while 嵌套里的循环变量忘了推进比起 for 循环while 嵌套更容易出现死循环。因为 for 会自动从 range 里取下一个值而 while 需要你自己在循环体里修改循环变量。如果你忘写j 1内层循环条件永远成立程序就卡死在那里。这种问题的排查方法很简单看内层 while 的条件变量在循环体里有没有被更新。还有一个隐患是条件写反比如应该是while j n你写成while j n那么一开始 j0 根本不会进循环程序不会死但结果就是内层一次都没跑输出少了一大截。所以每次都去做边界代入非常有必要。5.4 break 和 continue 只作用于当前层很多新手以为 break 能一下跳出所有循环这是一个经典误解。break 只终止它所在的那一层循环。如果你在两层嵌套的内层写 break外层循环还会继续执行下一轮内层会重新开始。如果确实需要一次性跳出所有循环有几种处理方式。最传统的是用一个标志位found False for i in range(n): for j in range(m): if condition: found True break if found: break也可以把嵌套循环放进一个函数里直接 return这样可以在任意位置结束整个函数自然也就跳出了所有循环。相比之下用 return 更干净。还有for...else的玩法它会在循环正常结束没有被 break时执行 else 块里的代码可以用来判断是否提前跳出了循环。这个功能用得好很省事但刚接触时容易混淆建议先掌握标志位再慢慢玩技巧。5.5 常见嵌套陷阱速查表下面这张表是我自己常给学员用的每次写嵌套前扫一眼能避开大部分低级错误。陷阱现象可能原因排查方法内层循环一次都没执行内层 range 起点或条件判断有问题代入外层变量的最小值验证少打印一行/一列range 尾边界写错忘了含头不含尾把 stop 加 1 再试输出顺序颠倒内外层顺序反了确认哪个变量变化更慢卡死无输出while 循环变量没更新检查循环体里是否有递增/递减结果里混入了不该有的组合内层范围没有依赖外层变量确认内层 range 是否用了外层变量做边界break 后还有多余输出误以为 break 能跳出所有层改用 return 或标志位6. 练习建议与个人心得6.1 给新手的练级路线光看文章学不会嵌套必须上手练。我建议按下面的顺序从易到难刷十个小练习每个练习都刻意用“五步拆解法”过一遍打印 4×6 矩形星号。打印直角在左下角的直角三角形。打印直角在右上角的三角形注意空格和星号两个内层循环。打印九九乘法表从下三角版改成完整版。遍历一个 3×3 二维列表求所有元素之和。找出二维列表中的最大值及其坐标。用嵌套生成坐标网格再筛选出 xy 等于偶数的点。手写冒泡排序并打印每轮比较的结果。用嵌套实现一个矩阵转置把行变成列。用双层循环模拟一个打卡签到表日期×人员标记出缺勤情况。做完这十个你基本就把嵌套的所有形态都摸了一遍矩形、三角形、依赖边界、二维遍历、排序扫描、矩阵运算。每个题做完之后再用“输出调试法”把循环变量打出来看一遍你会对嵌套的运行轨迹形成肌肉记忆。6.2 我在实际教学和写代码过程中踩过的坑最后分享几个我自己的体会。第一个是永远不要高估“看一眼就懂”嵌套代码一定要亲手敲一遍。我见过太多学员看书上例题时觉得简单一到自己写就卡住。因为看懂是在看别人的逻辑自己写需要重新组织变量关系完全是两码事。哪怕只是照着示例敲一遍感受都会不一样。第二个是关于缩进的习惯。我早期写嵌套时不太注意缩进的对齐结果在一个多层循环里找 bug 找了半小时最后发现是内层一个 print 少缩进了一格。从那以后我写的所有循环内层不超过三层一旦超过三层必然重构。嵌套层数越深代码的可读性越差出错概率越高。如果真的需要三层以上优先考虑拆函数。第三个是善用推导式但不要滥用。列表推导式写得好确实帅但新手容易把逻辑塞进一行最后没人看得懂。我的原则是循环体只有简单表达式时用推导式有复杂逻辑就老老实实写 for。代码首先是给人看的其次才是让机器跑的。好了这篇关于循环嵌套的经验分享就到这里。如果你正在被嵌套弄到怀疑人生不妨把上面的十个练习挨个做一遍做到第五个左右你就会有那种“通了”的感觉。循环嵌套不是什么高深的魔法它只是你把“重复”这件事拆成“外部重复”和“内部重复”两层来思考而已。想通了就真的掌控全局了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑