资讯详情

Python循环嵌套从入门到精通:执行顺序、缩进与break归属

📅 2026/10/20 19:33:07 | 华诺云谱 👁 阅读
Python循环嵌套从入门到精通:执行顺序、缩进与break归属
写 Python 代码不卡壳的十个里有九个都倒在循环嵌套上。单个 for 循环谁都会写闭着眼睛都能遍历一个列表一旦变成两层甚至三层循环脑子就开始转不过弯来到底哪层先跑哪层后跑缩进一格两格什么区别break 到底停的是哪个循环。这不是你笨而是循环嵌套本身就需要一种分层思考的脑回路没训练过的人突然面对它卡壳太正常了。这篇文章就把 for 循环嵌套这件事掰开揉碎讲清楚。我会从最底层的执行顺序讲起用几个必须练的经典案例带你走一遍完整过程再把缩进、变量命名、break/continue 归属这类最容易踩的坑全部列出来最后聊聊嵌套循环的性能问题和进阶写法。无论你是刚学 Python 的零基础新手还是学了一段时间但看到多层循环就发怵的自学者这篇文章都应该能帮你把这块硬骨头啃下来。1. 为什么需要循环嵌套一个重复中的重复问题1.1 单个循环解决不了的事先想一个问题如果让你打印一个三行三列的星号方阵长这样*** *** ***用单个 for 循环怎么写你可能会说那就 print 三次***呗代码就是print(***) print(***) print(***)这只解决了三行的问题。如果要打印十行十列呢虽然可以复制粘贴十次但这显然不是写代码该有的样子。更麻烦的是如果列数也要跟着行数变比如第一行 1 颗星、第二行 2 颗星、第三行 3 颗星那你连复制粘贴模板都不好使了因为每一行的内容都不一样。这个时候就会发现问题的本质是每个行都需要执行一遍打印一行星号这个动作而打印一行星号这个动作本身又需要循环多次拼接字符。行是一个重复行内部的字符又是一个重复。两个重复叠在一起单层循环就无能为力了必须用循环嵌套——外层循环管行内层循环管列。1.2 嵌套循环的执行顺序外层走一步内层走一整圈理解循环嵌套最关键的一点是搞明白它的执行节奏。很多人第一次接触的时候会理所当然地以为先执行完外层再执行内层这是最大的误解。真实的执行顺序是外层循环每执行一次内层循环都要完整地从头跑一遍。打个比方内层循环像分钟外层循环像小时。时间走一个小时分钟要转整整一圈时间走 60 个小时分钟就得转 60 圈。外层迈出一步内层绕场一周。我画过无数遍这个流程用文字描述就是for i in range(3): # 外层循环 for j in range(3): # 内层循环 print(i, j)这段代码的执行顺序是i 拿到 0进入外层循环体碰到内层循环j 从 0 开始跑打印 (0, 0)j 变 1还在内层循环里打印 (0, 1)j 变 2打印 (0, 2)j 的 range(3) 用完了内层循环结束外层循环体也执行完了i 变 1再次进入外层循环体又碰到内层循环j 重新从 0 开始打印 (1, 0)、(1, 1)、(1, 2)i 变 2内层循环再来一遍打印 (2, 0)、(2, 1)、(2, 2)i 的 range(3) 也用完整个嵌套循环结束注意到没有内层循环的 j 每一次都是从 0 重新开始的。它不会因为上一次已经跑到 2 就继续从 3 开始。这是嵌套循环和普通循环最不一样的心理模型。1.3 用行与列的思维看待嵌套循环理解了执行顺序之后再来看为什么嵌套循环在编程里无处不在。几乎所有二维结构的东西都能用嵌套循环来扫描一遍打印图案外层管行内层管列二维列表/矩阵外层遍历行内层遍历行里的每个元素批量处理数据外层遍历每个文件内层遍历文件中的每行记录组合枚举外层遍历第一个集合内层遍历第二个集合生成所有组合只要你在一个循环体里面还要接着重复做某事第一反应就应该是这里可能需要嵌套循环。它是一种降维打击的工具把原本需要写 N 遍的重复代码压缩成几行结构化的逻辑。我在教学过程中发现一个规律凡是能熟练用行和列来预演嵌套循环运行过程的同学后面学算法、学数据处理都会顺畅很多凡是硬背代码、不深究执行顺序的同学换个题目就懵。所以这一节的内容真的值得多花点时间吃透。2. 核心细节缩进、变量与执行节奏2.1 缩进决定结构结构决定命运Python 里缩进不是装饰是语法的一部分。循环嵌套的层级完全靠缩进来表达。同样是两行代码缩进不同逻辑完全不同for i in range(3): for j in range(3): print(i, j) print(内层结束啦)和for i in range(3): for j in range(3): print(i, j) print(内层结束啦)第一段代码里print(内层结束啦)和for j in range(3):是同一级缩进所以它是外层循环体的一部分内层循环每完整跑完一遍它才执行一次——总共执行 3 次。第二段代码里它和内层循环里的print(i, j)是同一级缩进所以它属于内层循环体每打印一对 (i, j) 就会执行一次——总共执行 9 次。就这一个缩进的位置差异结果差了 3 倍。说它是一字千金毫不夸张。提示写嵌套循环的时候建议把代码编辑器的缩进显示功能打开用空格缩进就统一用空格用 Tab 就统一用 Tab千万别混用。我见过太多报错IndentationError: unexpected indent的新手一查全是混用空格和 Tab 惹的祸。2.2 内外两层循环的变量职责别混用别污染嵌套循环里通常会有两个循环变量外层一个内层一个。常见的约定是外层用i内层用j三层就再加一个k。这个约定不是强制性的但它能帮你一眼看出当前的变量属于哪一层读代码的时候不用动脑子。有些同学喜欢两层循环都用同一个变量名比如这样for i in range(3): for i in range(3): print(i, i)这段代码能跑不会报错但逻辑上已经乱了。内层的i会遮挡外层的i外层循环的i在内层循环期间根本访问不到。如果后面你发现结果跟预期不一样排查起来会非常痛苦。所以我的建议是嵌套循环的每一层都有自己的循环变量名字要有区分度。还有一个容易忽略的坑内层循环里如果要引用外层的循环变量一般没问题因为内层作用域能访问外层变量但如果在内层循环里对外层变量重新赋值那就要特别小心。看这个例子total 0 for i in range(3): for j in range(3): total total i j print(total)这个total没有歧义它不受循环嵌套的控制只是被内层循环反复修改。真正危险的情况是你在内层循环里改变了外层循环用来控制循环的变量这会导致循环次数失控甚至死循环。后面第四节我会专门讲这个坑。2.3 循环次数的计算外层次数乘以内层次数嵌套循环的执行总次数是有明确数学公式的如果没有 break、continue 这些中断语句总的循环体执行次数 外层循环次数 × 内层循环次数。这个公式能帮你快速地估算一段嵌套循环的工作量。比如count 0 for i in range(10): for j in range(10): count 1 print(count) # 10010 × 10 100 次。三层循环就是三个数相乘10 × 10 × 10 1000 次。但要注意这个公式的前提是内层循环的次数是固定值或者和 i 无关。实际编程中内层循环的次数经常是依赖外层循环变量的比如count 0 for i in range(10): for j in range(i, 10): count 1 print(count) # 55这个例子中内层循环的 range 起点是i所以当 i0 时内层跑 10 次i1 时跑 9 次……最后总数是 1098...1 55 次而不是 100 次。这种内层次数依赖外层变量的写法在打印三角形、处理矩阵上三角等场景中非常常见计算次数时一定要按累加来算不能想当然地直接相乘。2.4 列表数据里的嵌套遍历形态循环嵌套最常见的应用场景是遍历二维列表。所谓二维列表就是列表里的元素还是列表比如一个三行四列的成绩表scores [ [88, 92, 79, 85], [95, 88, 91, 90], [78, 85, 83, 80] ]要打印所有成绩几乎所有人的第一版代码长这样for row in scores: for score in row: print(score, end ) print()外层循环遍历scores这个列表每次拿到的row是其中一个子列表内层循环遍历row每次拿到的score是具体的成绩数字。这种先取列表中的子列表再取子列表中的元素的两层结构就是二维数据遍历的骨架。从切片、遍历到修改所有二维数据处理都逃不开这种模式。我在后面实操环节会用一个更贴近实际场景的案例来演示你马上就能感受到这种写法的价值。3. 实操四个必练的经典案例这一节是本文的重头戏。我精选了四个最经典、最能练手、也最能覆盖循环嵌套各种细节的案例。每个案例我都会给完整代码、运行结果以及关键解释。建议你打开编辑器跟着敲一遍绝对比盯着手机看有效果。3.1 九九乘法表最经典的嵌套入门题九九乘法表几乎是所有编程教程都会提到的例子因为它天然就是一个行数递增、列数跟着变化的结构完美对应嵌套循环for i in range(1, 10): for j in range(1, i 1): print(f{j} × {i} {i * j:2}, end ) print()运行结果1 × 1 1 1 × 2 2 2 × 2 4 1 × 3 3 2 × 3 6 3 × 3 9 ...关键细节在第二行range(1, i 1)。因为第 i 行只需要打印到 i 为止所以内层循环的右边界是i 1。这个内层循环范围依赖外层变量的写法是九九乘法表的核心考点。end 表示不换行两个空格做分隔print()空调用表示换行。我刚开始学 Python 的时候九九乘法表抄了三遍才理解为什么内层要用i 1而不是10。后来想通了打印到第几列取决于现在是第几行。这个行决定列的动态关系就是循环嵌套里最需要建立的直觉。3.2 用星号画三角形与金字塔打印星号图案是另一个经典题型它比乘法表更直观也更考验对空格和星号数量的拆分能力。来看一个右对齐直角三角形n 5 for i in range(n): for j in range(n - i - 1): print( , end) for j in range(i 1): print(*, end) print()运行结果* ** *** **** *****这个案例的精华在于一层嵌套循环不够用了需要两层内嵌循环——一层打空格一层打星号。你能在这里看到嵌套循环不仅限于两层外层循环体里可以同时有多个并列的内层循环它们按顺序依次执行。再来看一个完整的金字塔n 5 for i in range(n): # 空格数量n - i - 1 for j in range(n - i - 1): print( , end) # 星号数量2 * i 1 for j in range(2 * i 1): print(*, end) print()运行结果* *** ***** ******* *********这里的两个数量关系是核心空格是n - i - 1星号是2 * i 1。你可以代入 i0 验证第 0 行有 4 个空格、1 个星号i1 时有 3 个空格、3 个星号。这种用数学关系控制输出的能力是画图案类题目的灵魂。我在带新人时发现一个高频错误好多人会把空格循环放在星号循环后面导致图案右半边凹了进去。记住空格一定要在星号之前打印因为输出是先打印的内容靠左。3.3 二维列表的遍历与求和图案类的嵌套循环练习的是输出控制接下来这个例子更贴近真实业务——处理二维数据。假设你有一个二维列表存储了三家门店一周七天的销售额你想算每天全部门店的总销售额以及每家门店的周总销售额sales [ [1200, 980, 1350, 1100, 1500, 1650, 1400], # 门店A [800, 920, 1010, 870, 1100, 1200, 950], # 门店B [1500, 1420, 1280, 1350, 1600, 1750, 1880] # 门店C ] daily_total [0] * 7 # 每天的全部门店总销售额 store_total [0] * 3 # 每家门店的周总销售额 for i in range(len(sales)): # i 表示门店 for j in range(len(sales[i])): # j 表示星期几 value sales[i][j] daily_total[j] value store_total[i] value print(每天全部门店总销售额:, daily_total) print(每家门店周总销售额:, store_total)运行结果每天全部门店总销售额: [3500, 3320, 3640, 3320, 4200, 4600, 4230] 每家门店周总销售额: [9180, 6850, 10780]你看一次双重循环同时算出了两组不同的聚合数据。这里的手法很典型内层循环每次能拿到一个具体元素根据你累加到哪个变量来决定汇总维度。如果内层循环里同时往daily_total[j]和store_total[i]里累加就相当于一次遍历两种统计口径都得到了。这个案例我给很多零基础学员演示过他们看完基本都能秒懂为什么二维数据要用双重循环——因为数据本身是二维的你自然需要用两个下标来定位。这类业务型的例子比打印星号更有代码感也更接近真实开发。3.4 用一个批处理场景串联所有知识最后一个案例是一个完整的小项目用来把前面所有知识串起来。场景是这样的一家网店要对所有订单做批量结算每个订单包含若干商品你想打印出每个订单的金额明细和订单总金额。orders [ [(键盘, 199), (鼠标, 89), (显示器, 1299)], [(U盘, 79), (硬盘, 499)], [(耳机, 299), (音箱, 459), (麦克风, 399), (声卡, 899)] ] for i, order in enumerate(orders, start1): total 0 print(f订单 {i} 明细) for name, price in order: print(f - {name}: {price} 元) total price print(f 订单总金额: {total} 元) print(- * 20)运行结果订单 1 明细 - 键盘: 199 元 - 鼠标: 89 元 - 显示器: 1299 元 订单总金额: 1587 元 -------------------- 订单 2 明细 - U盘: 79 元 - 硬盘: 499 元 订单总金额: 578 元 -------------------- ...这个案例和前面商场销售的例子异曲同工都是两层循环遍历外层列表项、内层明细。但这里多了一个重要的细节total变量的初始化位置在外层循环体内、内层循环外。为什么如果放在内层循环里每个商品都会把 total 清零一次最后算出来永远是最后一个商品的价格如果放在外层循环外所有订单的金额会混在一起根本无法区分订单 1、订单 2。判断一个变量该放在哪一层是嵌套循环里最容易出错的点也是最值得认真体会的点。我常用的方法是这个变量是想描述外层对象的维度还是内层对象的维度描述订单的就放在外层循环内描述商品的就放在内层循环内。4. 常见问题与排查技巧实录这一节的内容全部来自我实际面对过的报错现场。说真的循环嵌套的报错几乎都是同样的几个原因反复出现把它们记住了排查起来就是分分钟的事。4.1 缩进错乱逻辑错误的隐形元凶缩进问题有两种表现形式。一种是语法层面的代码一跑就报IndentationError这种反而好办看报错提示的行号就能修。另一种更可怕的是语法没问题、代码能跑、但结果完全不对——这种大多是因为缩进层级导致代码跑错了位置。举个例子你想统计每个子列表中大于 10 的元素个数data [[12, 5, 18], [3, 9, 15], [20, 7, 11]] counts [] for sub in data: count 0 for num in sub: if num 10: count 1 counts.append(count) print(counts) # [2, 1, 2]但如果把counts.append(count)的缩进往右挪一格变成跟if同级for sub in data: count 0 for num in sub: if num 10: count 1 counts.append(count) # 错每次符合条件的都 append 一次结果就会变成[1, 1, 1, 1, 1]这样的鬼东西。同样的代码一个缩进位置之差结果天差地别。注意用 VS Code 或 PyCharm 的同学强烈建议开启代码缩进可视化显示空格和 Tab。缩进错乱导致的 bug 很难直接看出来但可视化之后错误层级一目了然。4.2 在循环内部修改迭代变量死循环制造机这是一个非常隐蔽的坑。前面说过for 循环里用range的时候修改i的值并不会影响循环的下一次取值因为 range 已经生成好了一个序列i每次只是从序列里取下一个值。但如果你在循环体里用了列表或类似结构做迭代对象同时又修改了那个列表本身就可能会引发意想不到的问题。至于 dead-loop最常见的场景其实是 while 和 for 混用的时候。比如这样一段代码i 0 while i 5: for j in range(3): print(i, j) # 忘了写 i 1 → 死循环这是 while 循环的坑不是 for 嵌套的坑。for 嵌套里真正常见的是这种内层循环修改了外层用来控制循环次数的列表。比如tasks [1, 2, 3] for task in tasks: for i in range(2): print(task) if task 2: tasks.append(999) # 迭代时修改了被遍历的列表运行这段代码可能会卡死也可能会一直追加下去行为不可控。Python 的 for 循环是基于索引的它在遍历的同时修改列表会引发索引错乱很多情况下会产生死循环或跳过元素。安全原则不要在遍历列表的同时修改这个列表。如果确实想过滤或添加元素建议先复制一份列表再遍历或者改用 list comprehension 生成新的列表。这条经验从入门到进阶都适用。4.3 break 和 continue 到底作用于哪一层另一个高频困惑是 break 和 continue 的作用范围。很多初学者以为它们能让最外层循环也停下来实际上它们的生效范围是当前所在的这一层循环。也就是说break 写在哪个 for 里面它就结束哪个 for。看这个例子for i in range(3): for j in range(3): if j 2: break print(i, j)运行结果是0 0 0 1 1 0 1 1 2 0 2 1注意每一行打两个数j0 和 j1到 j2 时 break但这里 break 只结束了内层循环。等内层循环 break 之后代码回到外层循环i 继续变 1、变 2外层循环并没有停。如果你确实想在满足某个条件时跳出一整个双层循环那就要自己做一个控制标记found False for i in range(3): for j in range(3): if i * j 6: found True break if found: break print(i, j) # 2 3 吗不对条件是 i*j6结果是 (2, 3) 不成立实际是 (2, 3) 才成立注意 range(3) 只有 0,1,2这里我故意写了个容易混淆的例子实际range(3)里不含 3所以i * j 6永远不会满足。写代码的时候一定要时刻注意 range 的区间。如果真需要跳出多层循环Python 3.8 之后还可以考虑封装函数配合 return逻辑会更清晰。这个跳出多层循环的需求在实际业务里并不少见值得记一下这个控制标记的写法。4.4 常见问题速查表现象可能原因解决方法内层循环一次就结束外层也跟着结束了break 缩进位置错了写在了内层外面检查 break 的缩进层级结果比预期少很多内层 range 的起点/终点写错代入几个 i 值验证内层循环次数结果完全重复内层循环没有使用外层变量确认内层逻辑是否需要依赖 i报错 list index out of range用下标遍历时误用了外层长度多用for item in list结构少用下标数值统计比预期大累加变量初始化放在了外层循环外把累加变量移进外层循环内、内层循环外打印结果没有换行忘记在内层循环外调用print()内层结束后用一个空的print()换行这张表我建议收藏一下写嵌套循环卡住的时候拿出来对照比翻文档快得多。5. 循环嵌套的进阶玩法与性能提醒掌握了基础的嵌套循环你的 Python 之旅已经能覆盖绝大多数二维数据场景了。但想写得又快又好还有两个进阶点值得关注。5.1 用列表推导式替代简单的双重循环Python 有个特别的优势很多简单的双层循环可以用列表推导式一行搞定。比如你想把一个二维列表展平成一维列表matrix [[1, 2, 3], [4, 5, 6], [7, 8, 9]] # 传统双重循环写法 flat [] for row in matrix: for num in row: flat.append(num) # 列表推导式写法 flat2 [num for row in matrix for num in row] print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9] print(flat2) # 和 flat 完全一样列表推导式里的for row in matrix for num in row看起来顺序很反直觉但它确实就是嵌套循环的展开形式——第一个 for 对应外层第二个 for 对应内层。写熟之后会很爽但新手阶段我建议先用传统写法理解清楚再迁移到推导式否则容易把顺序搞反。5.2 嵌套深度与执行效率的关系前面说过双层固定循环的总次数等于两层次数相乘。这个乘法增长很快100 个数量的双层循环是 10000 次三层就是 1000000 次。在刷题或处理大数据的场景里把三层循环优化成两层执行时间可能相差几十倍。举一个常见例子假设你要在一个列表里找出两数之和等于某个目标值的所有数对。最直接的三层思维是遍历所有 i、所有 j、再验证一次但更好的写法是双循环就够甚至用字典做到单循环。嵌套循环不是越多越好能省一层就省一层。不过话说回来对初学者而言先保证代码正确再优化效率。很多同学一上来就想着什么优化结果写出了一堆看不懂的复杂代码反而更慢。我见过一个学员把一段简单的双层循环改成用一堆位运算跑是快了 20 毫秒但代码他自己第二天都看不懂了。适量优化是好事过度优化就是折腾。5.3 什么时候该把双层循环拆成单层有一类双重循环其实是伪嵌套——内外两层循环之间没有逻辑依赖纯粹是因为数据是二维的。这种时候Python 的itertools.product可以把嵌套循环扁平化成一个循环from itertools import product for i in range(3): for j in range(4): print(i, j) # 等价写法 for i, j in product(range(3), range(4)): print(i, j)product(range(3), range(4))会生成 (0,0)、(0,1)、(0,2)、(0,3)、(1,0)……所有组合。它的作用是把两层循环的表达变成一层逻辑上更线性读起来更像遍历一个集合的集合。这种写法在你需要嵌套枚举多个维度时特别有用比如生成两个列表的笛卡尔积。顺便说一句product这类工具是 Python 标准库自带的不需要额外安装任何东西直接 import 就能用。注意product适合维度较多、但每层之间无依赖的场景如果内层循环范围依赖外层变量比如九九乘法表那还是老老实实用传统写法硬套 product 反而更绕。结尾的话嵌套循环的肌肉记忆怎么练最后分享一点我自己的体会。循环嵌套这个东西本质上不是会不会写代码的问题而是能不能在脑子里预演循环过程的问题。你不需要天赋异禀只需要在初期刻意练习每看到一个嵌套循环先在纸上列出前几步的执行痕迹算出总的执行次数再看代码的输出是否和预演一致。练上 20 个例子这个能力就长在你自己身上了。很多人学循环嵌套之所以痛苦是因为急着背代码、急着跑结果跳过了预演这一步。我对每一个来找我问嵌套循环的初学者都说同一句话代码跑不出来不是问题代码跑出来了但你看不懂它为什么这么跑才是问题。你能把每一步执行顺序讲清楚嵌套循环你就算真的掌握了。这套方法我已经带过好几波新人验证过效果很稳定。你也不用一次练完所有案例挑九九乘法表和订单结算那两个例子先上手跑通之后再看星号图案和性能优化部分。等你能把一个三层循环的执行顺序在纸上完整写出来的时候你回头看今天的困惑就会觉得那只是一个小小的台阶而已。
📝

华诺云谱内容团队

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

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

你可能需要的服务

订阅华诺云谱资讯周报

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

↑