ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python循环嵌套详解:从执行顺序到实战案例一次搞懂

Python循环嵌套详解:从执行顺序到实战案例一次搞懂 先问一句你有没有过这种经历——学了Python的for循环和while循环感觉简单得很不就是重复做某件事嘛。结果一碰上循环嵌套看到两层、三层for叠在一起脑子里就开始浆糊到底是先跑哪个跑到第几步会换行为什么结果打印出来是一坨乱麻这个坎我也过过而且卡了挺久。当年我把for i in range(3)和for j in range(3)拼在一起的时候跟看天书一样。后来是靠一张张手动画表格、一遍遍加print调试才彻底理顺的。这篇就把那条弯路绕过去我直接把我自己的完整理解路径、练手代码、踩坑记录和调试方法都摆出来从晕头转向到掌控全局一条龙讲明白。这文章不是教科书是照着新手真的会遇到的场景写的。看完你会弄懂三件事嵌套循环的执行顺序到底怎么走、日常开发里它到底能干嘛、还有犯错之后怎么快速定位修掉。适合刚学完基础循环、准备进阶的读者也适合那种循环看得懂嵌套就拉胯的夹生状态。1. 循环嵌套到底在干什么——先扯清楚逻辑再动手1.1 嵌套的执行顺序外层转一圈内层转一整圈很多教程一上来就甩代码说这就是嵌套然后让你跑一遍看效果。跑完你记住了输出但根本没理解过程。我第一次就是这么学废的。咱们用最直白的方式拆解。看这段标准入门代码for i in range(3): # 外层循环 for j in range(2): # 内层循环 print(i, j)不急着想结果先掌握一句核心口诀外层循环每执行一次内层循环就要从头到尾完整跑一遍。你想象一下体育课排队报数。外层循环就是全班5列横队内层循环就是每一列从第1个人报到第5个人。你是老师你要一列一列地查查完第1列才走到第2列去从头报数——绝不会查到一半就跳列。所以刚才那段代码的流程是这样的i 0时外层进入循环体此时内层for j in range(2)启动j依次等于0、1分别打印(0,0)和(0,1)。内层循环结束外层第一次迭代也结束i变成1。i 1内层又从j 0开始打印(1,0)和(1,1)。直到外层i走完0,1,2三次整个嵌套才结束。这个从头再来的机制是理解嵌套里最重要的一件事。你只要卡住顺序就自己拿张纸写一行当前i几、当前j几、这次该打印什么手写两轮脑子就通了。1.2 为什么新手一看嵌套就晕——把行和列拆开想我后来发现大部分人对嵌套的理解困境不是语法问题是空间想象力问题。for i in range(5)到底代表什么for j in range(3)又代表什么只要你能把这两个循环对应到行和列整个嵌套就开始变得非常具象。想一想一张Excel表格。行号用i表示列号用j表示那么arr[i][j]就是定位到第i行第j列那个格子。外层循环控制走到哪一行内层循环控制这一行里从第1列走到最后一列。嵌套循环说白了就是一行一行地扫过去每一行内部再一列一列地扫。for i in range(3): # 行0到2 line for j in range(5): # 列0到4 line * print(line) # 打印完一行再换行这段代码我建议新手第一晚就敲一遍然后心里默念外层走行、内层走列、内层结束才换行。跑出来的结果是3行5列的*是不是像一个小矩形这就对了。很多图形打印题三角形、菱形、棋盘全是这个底层逻辑套壳。注意这里有一个很容易被忽略的细节——如果print(line)放在内层循环里面输出就会变成一列一列的*竖着排。换行时机直接决定了输出长什么样。写嵌套代码时先想清楚每行做什么、什么时候换行而不是急着敲键盘。2. 新手必写的练手程序三角形、乘法表与矩阵2.1 三步写出一个直角三角形直角三角形图案是嵌套循环里最经典的入门题几乎每个教程都有。它的代码量小但能完整暴露你对行号变化和循环次数关系的理解。for i in range(1, 6): # 5行 for j in range(i): # 第i行有i个* print(*, end) print() # 每行结束换行输出* ** *** **** *****它的关键在哪在range(i)。第1行i 1内层循环执行1次第2行i 2内层循环执行2次。行号和列数形成了联动这就是动态循环次数的精髓。你以为写嵌套是固定循环几次实际上内层循环的边界完全可以由外层控制变量i决定。如果你想要倒过来的直角三角形也简单——把range(i)改成range(6 - i)就行。第1行打印5个星第2行打印4个依次递减。这个用外层变量计算内层范围的思路后面写冒泡排序、写金字塔都会用到值得刻进DNA。2.2 九九乘法表缩进决定成败的经典案例谁没被九九乘法表折磨过但说实话它就是嵌套循环里最好的体检项目。你如果能不看答案独立写出来并且能讲清楚为什么换行位置在那儿你就算是真入门了。标准写法for i in range(1, 10): for j in range(1, i 1): print(f{j} x {i} {i * j}, end\t) print()想一想每一行输出的前几个式子第1行只有1 x 1第2行有1 x 2和2 x 2第3行有1 x 3、2 x 3、3 x 3。j的范围是1到i永远不超过当前的i这样才能形成左下三角形而不是全矩形。这里最容易翻车的点是end\t和print()的配合。end\t让式子和式子之间用制表符隔着不换行等内层循环全部结束print()再输出换行。如果漏了最后一个print()所有内容就会挤在一行如果把它写进内层循环每个式子后面都会换行直接变成一列电话簿。再提一个进阶想法如果你想自己控制对齐可以把结果格式化成字符串再拼接。比如str(i*j).ljust(4)这样每列宽度固定输出比\t更整齐。日常做表格类数据展示时这个思路非常实用。2.3 用嵌套遍历二维列表——就拿矩阵求和当例子图形题只是理论模型真正会大量使用嵌套循环的地方是二维数据结构。想象你有这么一份数据scores [ [90, 85, 78], [88, 92, 80], [70, 95, 87] ]这是三行三列的嵌套列表你该怎么把每个成绩都打一遍再算出总分答案就是双层循环。total 0 for i in range(len(scores)): for j in range(len(scores[i])): print(scores[i][j], end ) total scores[i][j] print() print(总分:, total)熟练以后还能用enumerate拿行列索引for i, row in enumerate(scores): for j, value in enumerate(row): print(f第{i}行第{j}列的分数是{value})如果你要的不是总成绩而是每个学生的平均分那就把内层循环改成按行求和算完一行直接算平均再进入下一行。你会发现只要理解了外层循环碰到的row本身也是个列表内层循环再对它遍历二维数据的操作基本就通了。之后再接触二维矩阵运算、图像像素遍历、CSV表格读取都是这个模式的变体。3. 循环控制进阶break、continue 到底管哪一层3.1 一坑接一坑break只会打断离它最近的那层循环嵌套循环学了几天后你就会开始往代码里加break和continue。这时候第二个大坑出现了很多人以为写一个break就能把整个嵌套都停掉结果代码只跳了半截。例子你想提前跳出两层循环写了这么一段for i in range(5): for j in range(5): if j 3: break print(i, j)你会发现i 0时打印(0,0)(0,1)(0,2)然后内层断掉但外层循环继续i 1时又来一遍。这个break只管了内层for j那一层外层压根没收到通知。用生活场景类比你在两栋楼里检查房间外层循环是一栋楼一栋楼走内层循环是一层楼一层楼检查。你在一栋楼的3层发现异常喊了一声停break结果只是这栋楼这层不查了你还是会走到下一栋楼去。想彻底停你得给楼长打电话通知所有楼都别查了。3.2 如何退出所有嵌套循环——标志位和return二选一既然单个break不够那就得用点花活。最常见的方法是引入一个标志位。found False for i in range(5): for j in range(5): if i * j 12: print(找到了条件, i, j) found True break if found: break核心逻辑内层break之后外层立刻用if found判断一下发现标志位变了自己也跟着break。这样两层循环都会退出。如果你是在函数里写这段代码更简单——return会直接结束整个函数连循环带函数全停。所以在实际开发里我倾向于把需要嵌套提前退出的逻辑单独包成一个函数然后用return代码看着干净逻辑也清楚。另外continue的规则跟break一样它也是只作用于离它最近的那层循环。内层里写continue只是跳过内层当前这次迭代外层该跑还是跑。写代码前先问自己一句我这层循环里的continue到底是想要它跳过哪个循环的哪一轮想清楚再下笔能省掉很多debug时间。3.3 带标志位的嵌套循环——质数判断的实战理解光说标志位可能有点抽象我拿一个绝大多数人都写过的例子来讲判断一个数是不是质数。新手第一版通常这么写def is_prime(n): if n 2: return False for i in range(2, n): if n % i 0: return False return True倒过来想一想如果你想用嵌套循环把所有1~20里的质数挑出来还要逐个检查那个找到一个因数就立刻停止检查当前数的行为其实就是一次典型的带标志位提前退出。写出来是这样for n in range(2, 21): is_prime True for i in range(2, int(n**0.5) 1): # 只要检查到根号n就够了 if n % i 0: is_prime False break if is_prime: print(n, 是质数)这里外层循环负责n 2到20内层循环负责从2试到sqrt(n)。一旦发现整除标志位is_prime变为False内层break出去外层把结果打印出来。这个模式在嵌套循环里判断是否存在某种特征的场景下非常通用——二维数组里查目标值、走迷宫找出口、表格筛选脏数据全是一个套路。4. 实战案例用嵌套构建邻接矩阵4.1 什么是邻接矩阵——为什么要写它我知道有些读者可能会问我学循环嵌套难道就是打印星星和乘法表吗当然不是。这里给一个能直接用在工作里的例子构建邻接矩阵。这个热词在搜索次数里不算低因为无论是写图算法Kruskal、Prim、Dijkstra还是搞知识图谱第一步都是把节点之间的关系整理成矩阵形态。邻接矩阵说白了就是一张n x n的表。想象你有4个城市编号0到3城市之间有路就写1、没路就写0。这张表里第i行第j列的数字就表示城市i到城市j是否有直连路线。构建它天然就是一个嵌套循环的任务外层走访每一行内层走访每一列的每个格子根据两个节点是否有关联填数字。4.2 从边列表到邻接矩阵——嵌套循环完整实现假设你的原始数据是一堆边比如(0,1)表示0和1相连(2,3)表示2和3相连。那么任务就是从这些边出发生成一个二维数组。n 4 # 节点个数 edges [(0, 1), (2, 3), (0, 2)] # 先初始化一个全部为0的n*n矩阵 adj_matrix [[0 for j in range(n)] for i in range(n)] # 遍历每条边把对应位置置1 for u, v in edges: adj_matrix[u][v] 1 adj_matrix[v][u] 1 # 如果是无向图两个方向都要填 # 遍历并打印矩阵 for i in range(n): for j in range(n): print(adj_matrix[i][j], end ) print()这里的双层循环体现在两个层面初始化时用了列表推导式嵌套打印时用了双重for遍历。实际使用中你还会遇到矩阵是否对称对角线置1还是置0有权图该存权重而不是1这些细节但核心的行到列的遍历填充思想完全一致。用列表推导式生成二维数组有个隐藏好处它比[[0]*n for _ in range(n)]更安全。你要是写成[[0]*n]*n看起来也是n*n的列表但每一行其实是同一个对象的引用改一个元素整列全变。这种引用共享的坑没踩过的人很难想象有多迷。新手第一次排查二维数组时经常怀疑人生题就出在这个*n的写法上。4.3 遍历矩阵时的积木式思考——可读性和效率的权衡构建完矩阵遍历的时候也值得花点心思。假设计算每个节点的度也就是有几条边连着它用嵌套循环的做法是degree [0 for _ in range(n)] for i in range(n): for j in range(n): if adj_matrix[i][j] 1: degree[i] 1这个操作的时间复杂度是O(n²)对稠密矩阵来说没法避免因为每个格子都得看一眼。但如果你在代码里遇到的图是稀疏图大多数节点之间互不相连直接用边列表统计会快得多。写代码的一个原则是**嵌套循环不是唯一方案它是最通用、最直观的方案。**当数据量变大时你就要想想能不能改用其他数据结构来绕过双重遍历。我实际开发里见过不少例子一上来就把几千乘几千的二维列表嵌套遍历好几遍结果跑十几秒。后来只是把数据源从二维矩阵改成一个哈希集合同样的功能几十毫秒就结束。这也是学习嵌套循环时应该同步建立的意识用嵌套能写对很好但能判断这地方值不值得用嵌套才是进阶。5. 优化思路列表推导式与替代方案5.1 用列表推导式优雅压缩嵌套for循环不是唯一表达嵌套迭代的方式。Python里最让新手直呼神奇的就是列表推导式里的嵌套写法和if过滤。举个例子你要取出二维列表里所有的偶数matrix [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ] evens [num for row in matrix for num in row if num % 2 0] print(evens) # 输出 [2, 4, 6, 8]这里面的顺序跟嵌套循环的顺序是一样的先for row in matrix再for num in row最后if num % 2 0筛选。你写列表推导式时就把for从左往右读当成普通嵌套循环的扁平化版本就行。当然不建议一上来就把所有嵌套都改写成推导式。当逻辑超过两次for加一次过滤时可读性会急速下降。那时候用普通循环加注释反而更友好。我的习惯是只有三层以下、且能一眼看出含义时才用推导式。5.2 别怕嵌套深怕的是不封装嵌套循环写多了以后很多人的代码会变成沟壑纵横的恐怖深渊最里头缩进七八层谁看谁懵。面对这种情况最好的办法不是继续叠而是把内层逻辑拆出去。老代码是这样的for i in range(len(orders)): for j in range(len(orders[i][items])): if orders[i][items][j][price] 100: total orders[i][items][j][price]你可以把遍历单个订单的全部商品并计算贵价商品总价抽成一个函数外层循环就变得非常清爽。def sum_expensive_items(order, threshold100): total 0 for item in order[items]: if item[price] threshold: total item[price] return total for order in orders: total sum_expensive_items(order)这种重构不改变核心逻辑但大幅度降低心智负担。嵌套循环本身没错错的是把所有东西都堆在一起。每层循环之间尽量保持内层只关心当前这层数据而不是去修改几十个外层变量代码会好维护很多。5.3 嵌套循环的性能提示——小技巧能救大场面纯Python做大数据量嵌套循环性能其实一般。跑2000x2000的矩阵双重遍历每格做一次简单运算大约要几十秒到一分钟。遇到这种场景最简单的优化是上numpy它底层用C实现相同操作可能只要几毫秒。import numpy as np arr np.zeros((2000, 2000), dtypeint) # 如果只是要按条件批量赋值根本不需要Python双层循环这不是说让你放弃学习嵌套循环——恰恰相反只有学会了嵌套循环你才能理解numpy的向量化操作到底帮你省了什么。你看见arr[u, v] 1这种代码时才不会觉得是魔法而是知道它背后本来应该是两层for循环在做填充。基础功和工具并不矛盾是一个递进关系。6. 新手最容易踩的坑与排查技巧实录6.1 常见错误速查表现象可能原因解决思路只打印矩形的一部分内层range边界设错比如range(i)写成了range(i1)手推一行行检查看第1行打印几个元素所有内容挤在一行忘了在print()里加换行或者换行写进了内层循环检查缩进位置print()应与内层循环对齐改了二维列表某一格整列都变了初始化用了[[0]*n]*n行与行共享引用改用列表推导式[[0]*n for _ in range(n)]break没退出整个嵌套break只作用于最内层循环用标志位或抽函数return循环次数比预期的多/少range的起点或终点搞错或者下标从1开始了打印i和j的值进行验证内层循环变量名与外层冲突都用i导致相互覆盖运行结果诡异外层用i内层用j不要偷懒这些坑我基本都亲自踩过。特别是那个[[0]*n]*n我当年因为它在做邻接矩阵时差点以为自己的图论算法写错了排查了一个晚上最后print出矩阵才发现每一行都是同一根指针。6.2 调试嵌套循环的实用方法——print大法真不过时排查嵌套循环最高效的手段永远是在关键位置打印变量。你不需要上多牛的调试器加两三行print就能看清执行过程。for i in range(3): print(f外层循环开始: i{i}) for j in range(i 1): print(f 内层循环: i{i}, j{j}) print(f外层循环结束: i{i})注意打印的缩进。缩进不改变实际输出但能帮你区分这里面是外层在跑还是内层在跑。如果代码已经写完但结果不对我一般先把所有业务逻辑注释掉只打印行列编号确认循环边界本身是对的再回头检查内部计算。这叫做先定位结构错误再定位逻辑错误能省下大量瞎猜的时间。如果是深层嵌套光加print可能刷屏太多。一个更优雅的办法是条件打印只在特定条件下才输出。比如怀疑i2, j3时出问题就写if i 2 and j 3: print(...)。这样既能看到关键节点的变量状态又不会被海量日志淹没。6.3 环境相关的两个小插曲——装好了不显示怎么办文章开头我说过有很多读者还在折腾Python安装。结合搜索热词里那一堆python安装python环境变量配置vscode python环境配置我说一个非常普遍且容易让人崩溃的场景你费了半天劲装好Python写了个循环嵌套的脚本在VS Code里点了运行结果黑窗口一闪而过什么也看不到。这通常不是代码问题也不是循环嵌套写错了而是程序执行完窗口直接关了。解法是在脚本最后加一行input(按回车键退出...)或者用VS Code自带的Run Python File配置让输出到调试控制台而不是外部终端。顺带提一句如果命令行直接敲python xxx.py能正常打印反而是在编辑器里配置有问题那优先查编辑器终端和Python解释器路径。环境这块确实很劝退但我自己的经验是花第一个小时把解释器、VS Code、pip弄明白比后面反复折腾节省至少几十个小时。你打开终端输入python能进到交互式环境、按CtrlZ能退出就说明基础环境没问题了。剩下的就是踏踏实实写循环跑代码看输出。7. 写在最后的一次实操复盘我记得自己彻底把嵌套循环搞明白是因为一个很小的代码片段。当时我在做一个小应用需要打印一个每行长度递增、每行内容是一组编号的数据结构。我一开始怎么都写不对后来把print插到内层循环亲眼看着i和j每次运转的值一点一点变化那一下突然就通了。后来我又陆续用嵌套循环处理过订单明细、生成过权限矩阵、写过小组排班每一种玩法都离不开外层行、内层列、按需换行、按需跳出这几板斧。说句实在话嵌套循环在项目里更像一把通用螺丝刀——它没有那么高端但你绕不开。你不需要害怕它你只需要跑起来、打断点看过程、把错误当成线索去追踪几轮下来自然就有手感。如果你看完这篇正准备上手练我建议你今晚只做一件事把九九乘法表独立写出来。不要复制自己敲错了就加print看过程直到不看答案能写对。等你打通这一关再回头看所谓的循环嵌套四个字真的就只是外圈跑一次、内圈跑一圈而已。
返回列表