直接写正文不加说明。 Python的循环语句是很多人从“会写脚本”跨到“写干净代码”的一道坎。老实说我在初学的头一两个月里循环用得最多的地方就是遍历列表打印结果直到后来写了一个批量重命名文件的小工具才真正意识到循环不是“反复执行几遍”那么简单——它背后是代码的组织方式、数据的遍历方式、还有程序在什么时机跳出、什么时候兜底。这篇文章就把循环的完整细节梳一遍从for和while的底层思路到range参数、else子句、嵌套循环的坑再到推导式和迭代器怎么替代循环适合把Python语法看过一遍、但写循环时总感觉不踏实的同学也适合想系统梳理循环知识点的人。1. 为什么循环是Python学习的分水岭1.1 循环解决的是“怎么重复”的问题程序里最常见的需求不是计算11而是“对一批数据做同样的处理”。比如有100个文件名要加前缀有10个网页要抓标题有20个字段要校验。循环的本质就是把“重复”这件事交给机器让人只需要写一次处理逻辑。很多初学者一开始写代码会习惯复制粘贴。比如print(第1次) print(第2次)这当然能跑但一旦次数变成变量或者要处理的数据来自外部输入复制粘贴就完全失去意义。循环的价值不只是省代码而是让程序在处理“数量未知”的数据时依然稳定。你会慢慢发现所有跟批量、轮询、累积、搜索相关的逻辑几乎都离不开循环。Python里有两种主力循环for和while。严格来说还有嵌套循环、循环里的break和continue、循环的else子句但核心骨架就是这两种。理解了它们的差异很多场景下你都能马上判断该用哪种。1.2 用生活场景理解for和while我常给身边的朋友打一个比方for循环像“按清单挨个处理”清单上有多少项就处理多少项处理完自然结束。while循环像“守着某个条件条件为真就一直做”比如等地铁等车来了就上车没来就继续等。对应到代码里for循环适合遍历已知序列列表、字符串、字典、文件行。while循环适合需要反复判断条件的场景重试连接、等待输入、倒计时。当然Python里两者有很多时候可以互换但选对了会让代码更好读也更容易避免死循环。举个例子你要对列表里的每个数字加1nums [1, 2, 3] for i, n in enumerate(nums): nums[i] n 1这里用for很自然。但如果你想不断接收用户输入直到用户输入exit才停止用while就比for清晰while True: cmd input(输入命令exit退出: ) if cmd exit: break等你自己写多了就会发现for是“把一件事做完”while是“只要条件满足就一直做”。这两种语义上的差别决定了代码的表达力。2. for循环Python里最常用的循环2.1 for循环的基本语法Python的for循环和C语言差别很大。C语言里for通常写成for(int i0;in;i)Python里for更像“foreach”直接遍历可迭代对象中的元素。基础结构如下for item in iterable: # 处理item这里的item会依次被赋值为iterable里的每个元素。iterable可以是列表、元组、字符串、字典、集合、文件对象甚至生成器。我见过不少刚转Python的人一开始会纠结“我怎么拿到下标”。因为如果直接for n in nums只能拿到值拿不到序号。但Python提供了一个自带函数enumerate可以同时拿到下标和值fruits [苹果, 香蕉, 橘子] for idx, fruit in enumerate(fruits): print(idx, fruit)输出0 苹果 1 香蕉 2 橘子如果你确实只关注下标也可以写成for i in range(len(fruits))但这通常没必要。直接用for fruit in fruits更符合Python的遍历习惯只有在需要同时修改列表元素或者访问相邻元素时才考虑用下标。2.2 range的用法与参数细节range可能是for循环里最常搭配的函数。它生成的是左闭右开区间。看几个例子range(5) # 0, 1, 2, 3, 4 range(2, 5) # 2, 3, 4 range(0, 10, 2) # 0, 2, 4, 6, 8 range(10, 0, -2) # 10, 8, 6, 4, 2range最多接三个参数起点、终点、步长。终点不包含在结果里这是新手最容易踩的坑。比如想打印1到5有人会写range(1, 5)结果少了5应该写range(1, 6)。为了记住这点我习惯把它理解为“到终点就停但不到终点”。步长还可以是负数可以实现倒序。不过倒序遍历通常直接用reversed()更好读for i in reversed(nums):。range还有一个细节是它返回的对象不是列表而是range对象特点是你只能遍历它不能直接修改它。如果你非要一个列表包一下list(range(10))就行。for循环里的range最常见的用途就是“重复做N次操作”for _ in range(100): do_something()这里下划线代表“我不关心循环变量的值”单纯想执行100次。这个写法很Pythonic而且能够避免定义出一个用不到的变量。2.3 遍历字符串、列表、字典的实战字符串本身是可迭代对象所以可以直接for遍历得到每个字符word hello for ch in word: print(ch)列表的遍历最简单直接for item in items。但有个细节如果要在遍历过程中删除元素直接删除会导致跳项或者索引错乱。经典做法是遍历副本然后删原列表items [a, b, c, d] for item in items[:]: if item b: items.remove(item)更推荐的方式是用列表推导式重建列表后面会讲。字典的遍历方式稍微丰富一些默认遍历的是键d {name: 张三, age: 20} for key in d: print(key, d[key])还有三种常见写法for key in d.keys(): # 键 for value in d.values(): # 值 for key, value in d.items(): # 键值对遍历字典的键值对时注意变量名要成对出现否则容易ValueError。另外遍历字典的过程中不要直接修改字典的大小新增或删除键那会触发RuntimeError。我踩过这个坑后来都是先复制键列表再遍历键列表修改字典。3. while循环条件控制的灵活用法3.1 while循环的结构while循环的结构更简单while condition: # 循环体只要condition为真循环体就会一直执行。每次执行完成后重新计算condition如果还是真继续执行直到condition为假。这里最需要警惕的是死循环。写while时必须确保循环体里存在能改变条件的手段比如计数器递增、输入状态改变、break触发。count 0 while count 10: print(count) count 1一旦忘了count 1程序就会无限打印0最后只能强制中断。还有另一种情况条件本身是无穷真的比如while True。这种写法本身没错但循环体内部必须有break出口否则就是真正的死循环。有一种常见的疑问“while这个条件能不能写成复杂表达式”当然可以比如while len(queue) 0 and not stop_flag:使用and、or、not组合多个条件都行。要注意运算符优先级拿不准就加括号。3.2 经典场景轮询与重试很多实际场景都适合用while。最典型的是重试机制。比如请求某个外部服务可能临时失败你希望最多重试3次每次间隔2秒retries 0 max_retries 3 while retries max_retries: try: fetch_data() break except Exception: retries 1 time.sleep(2)另一种是交互式菜单choice while choice ! q: choice input(请输入选项q退出: ) handle(choice)这类场景如果非要用for硬套写起来会别扭。while表达“直到满足某个停止条件”更贴切。还有一种稍隐晦的用法把while当作“哨兵循环”用一个标记变量控制退出比如done False循环体内处理完某个任务后设置done True。这在读文件、事件处理中很常见。4. break、continue、else循环的控制与兜底4.1 break和continue的差异break是立即终止整个循环不管后面还有没有迭代次数。continue是跳过当前这一次迭代的剩余代码直接进入下一次迭代。两者的区别一个类比“退出整个会议”一个类比“出去接个电话再回来继续开会”。示例for i in range(10): if i 3: break print(i) # 输出 0 1 2for i in range(5): if i 2: continue print(i) # 输出 0 1 3 4使用break时要注意它只跳出当前这层循环。如果你在嵌套循环里想跳出两层break只能让你退出内层循环外层还会继续。解决这个问题的常见方案是设置一个标志位或者使用“提前return”封装成函数。我不建议滥用标志位逻辑变得很绕很多时候可以抽出独立函数直接return。continue也有个注意点在while循环里continue会直接跳到条件判断不会执行continue之后的语句所以循环变量的更新如果写在最后会跳过导致死循环。i 0 while i 3: i 1 if i 2: continue print(i)如果你把i 1放在print之后遇到continue就不自增了i一直不变死循环。因此写while时我习惯把计数器更新放在条件判断之前或者放在continue之前反正要确保continue不会跳过更新步骤。4.2 循环的else子句循环的else是Python一个很有特点的语法很多其他语言没有。它的含义是如果循环正常结束没有被break打断就执行else块。如果循环被break跳出else块不执行。for n in range(2, 10): for x in range(2, n): if n % x 0: break else: print(n, 是质数)这里内层循环如果找到因子就break不执行else如果没有找到因子整个循环跑完else就会执行。这个写法避免了“flag”标记变量能直接判断一个数是质数。初看觉得有点反直觉但用多了会觉得非常顺手。while也有elsei 0 while i 3: i 1 else: print(循环正常结束)需要提醒的是循环的else含义跟if-else完全不同很多人初次见时容易理解错。记住核心判断依据是“有没有break”。5. 嵌套循环与综合案例5.1 嵌套循环的消耗与优化嵌套循环就是循环里套循环。最典型的情况是双层循环遍历二维数据比如矩阵、坐标。示例matrix [ [1, 2, 3], [4, 5, 6], ] for row in matrix: for cell in row: print(cell)嵌套本身没有问题但要警惕三层以上以及大数据量下出现的性能问题。如果外层循环有m次内层循环有n次总执行次数就是m×n。要是三层嵌套就是m×n×k。哪怕每次操作很快数量一上来也会卡顿。我在实际写代码时如果发现有两层以上的嵌套循环会先思考能不能减少内层循环的次数。比如一个常见场景两个列表a和b要找出同时出现的元素。最粗暴的方法是双层循环for x in a: for y in b: if x y: result.append(x)这个写法时间复杂度O(n*m)。如果a和b都很大就会很慢。更好的办法是转成集合做交集result list(set(a) set(b))代码更短速度也快得多。嵌套循环不是不能用而是要知道它贵在哪并且能想到替代方案。5.2 案例打印九九乘法表九九乘法表是练习嵌套循环最经典的题目。基本思路是外层循环控制行数1到9内层循环控制当前行中的列数1到行号在打印时用格式化控制对齐。for i in range(1, 10): for j in range(1, i 1): print(f{j}x{i}{i*j}, end\t) print()这个代码有两个关键点第一个是内层range的终点是i 1因为range不包含终点所以当i1时内层只有j1当i9时j从1到9。第二个是end参数默认print结尾换行改成制表符就能保持同一行等内层循环结束后再打一个空print换行。运行结果就像我们小时候背的口诀表。除了这个基础版还可以练习倒序、上三角、下三角等变体来加深对嵌套循环控制的理解。比如加上格式宽度print(f{j}x{i}{i*j:4}, end)这个:4表示左对齐占4个字符宽度输出更整齐。6. 实用技巧、避坑清单与性能提示6.1 常见错误与排查写循环时最常踩的坑我整理成一张清单错误场景现象解决办法range终点写错少遍历一次记住左闭右开或使用len直接配合下标while忘记更新条件变量死循环确保循环体内有i1或break遍历列表时删除元素漏处理/索引错位遍历副本或改用推导式continue跳过循环变量更新while死循环把变量更新提到continue之前break只跳出内层循环外层继续执行用函数return或标志位控制遍历字典时修改键RuntimeError先list(d.keys())再遍历排查循环问题还有一个通用思路先在循环开头打印每次的变量值但数据量大时别打印太多。更好的方式是缩小范围比如把range(1000)改成range(5)先确认逻辑正确再放大数据。6.2 生成器、推导式与循环替代Python里很多循环其实可以被推导式替代写法更简洁。列表推导式的基本格式new_list [expression for item in iterable if condition]比如# 原来的循环 squares [] for i in range(10): squares.append(i ** 2) # 推导式 squares [i ** 2 for i in range(10)]推导式简化了“创建列表-循环-append”三行代码而且可读性很好只要条件不太复杂。类似的还有字典推导式d {key: value for key, value in pairs}生成器表达式则是把方括号换成圆括号得到的不是列表而是生成器。它按需计算适合大数据量场景避免一次性占用太多内存total sum(i ** 2 for i in range(1000000))这里用生成器做惰性求和比先构建一个100万元素的列表再求和要求内存小得多。虽然推导式能替代一部分循环但并不是所有循环都适合推导式。如果循环体里有多行逻辑、需要break或修改其他变量强行写推导式会很丑。我个人的判断标准是if还是单个表达式且逻辑能一行说清楚才用推导式。再提一个常见误区推导式本质还是一个循环嵌套推导式也一样。例如[ (x, y) for x in range(3) for y in range(3)]执行次数是3×39次它并不比双层for快只是写起来短。高性能场景下该用算法优化还是得优化不能指望用推导式解决性能问题。6.3 性能实测与思路很多人关心“for和while哪个更快”。在CPython里循环本身的性能差异其实不大真正的消耗在循环体执行的操作上。但有几条实践心得第一优先使用内置函数而不自己写循环。比如累加一个列表的和自己写循环for x in nums: total x不如直接用sum(nums)。内置函数底层是C实现不仅快还更简洁。类似的还有max、min、any、all、map、filter等能直接用就不手写。第二减少循环体内的重复计算。比如for i in range(1000): value expensive_function(i) # 如果expensive_function不依赖i应该提到循环外如果函数昂贵先算一次放在循环外。另外一个常见优化是提前计算列表长度避免每次循环都调用len虽然len本身开销小但习惯上还是要减少不必要的重复操作。第三用局部变量引用全局函数或变量。例如在循环里经常用到math.sqrt可以先sqrt math.sqrt再在循环里用sqrt(...)这样省去每次查找全局名字的开销。这个优化幅度不大但在数据量极大的时候效果可以感受得到。第四循环里拼接字符串不要用特别是字符串较长、次数较多时。字符串在Python里是不可变对象每次都会生成一个新字符串效率差。典型场景是拼接10000个字符串用列表收集再.join(list)会快得多。这也是for循环中很容易踩到的性能坑。从实际工作来看循环性能优化要优先提升算法复杂度而不是纠结微小的语法差异。把一个双层循环改成集合操作提升的是数量级把循环里的字符串拼接换成join提升的是常量级。先把能改算法的地方改掉再去抠细节。我个人在实际操作中还有一个习惯写任何循环时都会问自己三个问题。第一我是在遍历一个已知的集合吗如果是for更自然。第二我是在等待某个条件吗如果是while更合适。第三这个循环能不能用内置函数或推导式替代如果可以优先替代。这套判断流程看起来简单但对新人尤其管用能够帮你从一开始就写出更干净的循环代码。最后分享一个小技巧如果你经常需要循环遍历多个序列用zip可以成对遍历更优雅names [张三, 李四, 王五] scores [88, 92, 76] for name, score in zip(names, scores): print(name, score)另外如果只是想让循环“空转”一会儿比如简单延迟可以用for _ in range(1000000): pass。但要注意这种忙等待会占用CPU如果真的是等待优先用time.sleep。循环的知识点不算多但组合起来能产生很丰富的逻辑。把for和while的区别想透把break、continue、else的语义记牢再动手写几个带嵌套、带中止条件的Demo你的Python基本功就扎实了一大截。后面不管是写爬虫、数据处理还是写点自动化脚本循环这一关过了其他语法学起来会顺很多。