
学Python最让人感到“开始有点爽”的节点我个人的体会是从循环语句开始的。前面学变量、学数据类型都是在认识砖块和水泥到了循环你才算真正开始盖房子。就拿最常见的一个场景来说你想打印1到100的数字没有循环你得写print(1)、print(2)……这样写下去写到第100行而你学会for循环之后短短几行代码就搞定了。这就是循环语句最直观的杀伤力——它能帮你处理所有“重复做某件事”的需求。本篇文章围绕Python循环语句的学习进行一次系统拆解适合刚开始接触编程语法的新手也适合学了一段时间但总是理不清for和while边界、遇到死循环就慌的同学。我会把循环相关的核心语法、执行过程、避坑经验全部掰开揉碎讲清楚看完之后你完全可以照着写、照着练。我见过很多学习者卡在循环这一步原因其实不是笨而是没搞清楚循环的本质。循环本质只有一句话让计算机反复执行一段代码直到某个条件发生改变。这句话听起来很简单但一旦和break、continue、嵌套这些机制混在一起新手就开始懵。这篇内容会从for到while再从三大控制语句到常见报错与排查方法梳理出一条完整的学习路径跟着走一遍基本就能把循环系统搞明白。1. for循环我最推荐的遍历神器1.1 range函数与数字序列Python里最常见的循环写法就是for循环。很多从C语言转过来的朋友第一次看到for i in range(10)这种写法时会有点不适应因为C语言的for循环写法是for(int i 0; i 10; i)条件、初始化、步进全写在一起。Python的for循环则更像是“遍历”它会逐个取出一个序列里的元素然后让变量依次等于这些元素。range(10)生成的就是一个从0到9的数字序列。for i in range(10): print(i)这段代码会打印0、1、2一直到9一共10个数字不会打印10。这个细节很重要很多刚接触的同学第一次跑的时候都会困惑“为什么没有10”其实是因为range(n)生成的是[0, n)这个区间左闭右开。range函数还有两个进阶用法range(start, stop)和range(start, stop, step)。比如要打印100以内的偶数可以用range(0, 100, 2)从0开始每次加2到98结束。再比如要倒着打印10到1可以用range(10, 0, -1)注意当步长为负数时stop值也是开区间所以想打印到1就得把stop写成0或写成1都可以要看具体写法实操中我会写range(10, 0, -1)来打印10到1。关于range到底返回的是一个怎样的对象Python 3里range返回的是range对象它是个可迭代类型不是列表。你要是直接print(range(10))会看到输出range(0, 10)而不是[0, 1, 2...]想转成列表需要包一层list()。不过在实际使用中我们大多数情况下直接把它丢进for循环里遍历就行不需要转列表。1.2 遍历字符串、列表与字典的常用姿势for循环真正的魅力在于凡是能放进去遍历的对象它都能逐个取出来。字符串可以遍历每次取一个字符name python for ch in name: print(ch)拿到的是p、y、t、h、o、n这六个字符。这个特性在处理文本时非常方便比如统计一个字符串里某个字符出现了几次直接遍历字符串然后逐个判断就行。列表遍历最常见students [张三, 李四, 王五] for student in students: print(student)这个写法直观到不需要解释。但到字典这里就容易搞混了。直接for key in dict拿到的其实是字典的键不是键值对。如果你想让循环同时拿到键和值需要用dict.items()方法scores {语文: 90, 数学: 85, 英语: 88} for subject, score in scores.items(): print(subject, score)items()返回的是每个键值对元组在循环里用两个变量subject, score去分别接收键和值这叫元组解包。类似的用法还有dict.keys()只拿键dict.values()只拿值按需取用。1.3 for循环的嵌套与else分支嵌套循环是很多初学者的一道小坎。所谓嵌套就是在循环体里面再写一个循环比如乘法表for i in range(1, 10): for j in range(1, i 1): print(f{j} x {i} {i * j}, end\t) print()外层的i控制行数内层的j控制一行里打印几列end\t让每个乘法结果之间用制表符而不是换行分隔内层循环结束后用print()换一行。这段代码的输出就是标准的上三角乘法表。嵌套循环的逻辑重点在于外层循环每走一次内层循环要完整地全部走一遍理解了这句话嵌套循环就没有秘密了。还有一个比较冷门但偶尔会用到的语法for...else。当循环正常结束没有被break打断时执行else块中的代码for i in range(5): if i 3: break else: print(循环没有被中断)上面这个例子因为i等于3时触发了break所以else块不会执行。这个语法在写“查找是否存在某个元素”这类场景里很好用。比如要检查一个列表里有没有负数用一个for循环扫描列表如果找到负数就break如果整个扫描完都没有负数就进入else分支做对应的处理。这种写法比用一个标志位变量去记录“是否找到”要简洁不少代码的可读性也更好。2. while循环条件被满足之前一直干下去2.1 while循环的执行逻辑与基本语法for循环擅长处理可迭代对象而while循环更适合处理“我不知道要循环多少次只知道什么时候该停下来”的场景。它的语法结构非常简单while 条件表达式: 循环体代码执行过程可以这样理解每次进入循环前先判断条件是否为真为真就执行循环体执行完再跳回开头重新判断条件条件为假就跳出循环继续执行后面的代码。来看一个最简单的计数器i 1 while i 5: print(i) i 1这段代码打印1到5。注意最后一行i 1不少初学者第一次写while循环的时候会忘掉这行结果程序跑成了死循环。因为这个条件i 5永远为真i永远不会变大循环永远结束不了。写while循环的时候一定要确认循环体中有某个操作能改变条件表达式的值否则就是死循环。2.2 交互式循环不知道循环次数时的经典案例while循环最经典的scenario就是与用户交互。比如写一个猜数字的小游戏让用户不断输入数字直到猜对才退出secret 42 while True: guess int(input(请输入你猜的数字)) if guess secret: print(恭喜你猜对了) break elif guess secret: print(大了再试试) else: print(小了再试试)这个例子用了while True这种写法本身是个无限循环靠break在特定条件满足时跳出。这种“先无限跑遇到退出条件再break”的模式在开发中是相当常见的比如写一个输入exit退出的命令行小工具就很顺手while True: command input(请输入指令) if command exit: print(再见) break print(f执行指令{command})相比for循环while循环的条件掌控感更强适合处理轮询、等待状态变化、持续采集数据这类需求。在实际项目里比如从传感器读取数据直到指定阈值、重试网络请求直到成功这些都是while循环的看家本领。2.3 while与else的搭配以及它和for的边界跟for一样while也可以搭配else使用同样表示“循环体没有被break中断时会执行else块”i 1 while i 3: i 1 else: print(循环正常结束)这个知识点了解即可实际项目中while...else的出场率远低于for...else我写代码这么长时间while...else用的次数一只手数得过来。那么for和while到底怎么选我给一个很实用的判断标准如果你要处理的对象是一个明确的数据集合列表、字符串、字典、数字区间用for如果你是根据条件决定循环次数比如某个变量的值达到了某个阈值就停用while。这个标准基本覆盖了90%以上的场景。还有一种情况你想写一个“死循环”直到某事件发生那明显是while True更自然。把这两句话记在脑子里选起来就不会纠结了。3. break、continue与pass控制循环流的三个关键工具3.1 break跳出整个循环break的作用是立即终止当前整个循环不管循环现在执行到第几次也不管条件还没不满足直接跳出循环体继续执行循环之外的代码。来看一个用途在列表里找第一个能被7整除的数numbers [3, 5, 8, 14, 21, 28] for num in numbers: if num % 7 0: print(f找到了{num}) break这个循环从头扫描列表找到14的时候触发break打印结果后立即停止循环21和28不会再被检查。如果没有break循环会一路跑到底。这种“找到就停”的写法处理搜索类需求时效率很高避免不必要的后续遍历。注意break只能跳出离它最近的一层循环这个点特别关键。如果你在嵌套循环的内层写break它只会终止内层循环外层循环还会继续跑。举例来说for i in range(3): for j in range(3): if j 2: break print(i, j)内层循环在j等于2时跳出但外层循环没有受影响会继续执行i从0到2的全部过程。如果想从内层循环直接跳出所有外层循环单靠break做不到一般会用标志位或者把逻辑封装进函数用return返回。3.2 continue跳过本次迭代continue的作用和break完全不同它不会终止整个循环只是跳过当前这一轮剩下的代码直接进入下一轮循环。看个过滤负数的例子data [3, -1, 5, -2, 8] for num in data: if num 0: continue print(num)这个循环遇到负数时直接跳到下一个元素负数不会被打印。结果输出3、5、8。在数据清洗的场景里continue几乎是标配操作——遇到脏数据跳过保留有效数据做后续处理。写continue的时候有一点容易让人困惑如果循环体里还有其它的计数逻辑continue会跳过这些逻辑直接进入下一轮。所以除非你确实想把remainder of this iteration全部跳过否则别图省事把不该跳过的语句写在continue后面。3.3 pass占位符的艺术我见过不少同学刚学pass的时候很疑惑“我没看懂pass是什么用”其实pass就是一个什么都不做的占位语句。它不是为了控制逻辑而是为了保持语法完整性。为什么需要它因为Python的循环体、函数体不能完全为空如果你写了if条件还没想好做什么或者循环逻辑还没写完不放任何语句会直接语法报错。这时候放一个pass进去for i in range(10): pass # 先占位等会儿再写逻辑这个代码不报错但什么也不干。拿来做占位符、临时跳过还没实现的逻辑非常实用。写大型项目时分步开发局部逻辑还没写到位先用pass占住位置后面再回来补这个用法我觉得是pass最核心的价值。有些初学者会想着用注释来代替pass但实际上注释并不能弥补语法结构该放pass的时候还得放。3.4 三个控制语句的对比与应用场景语句作用循环是否继续典型场景break立即终止整个循环终止找到目标元素、退出交互式输入continue跳过本次迭代继续下一轮数据过滤、跳过异常数据pass什么都不做正常继续占位符、保持语法完整这三者在同一个循环体里是可以搭配使用的比如for...if...elif...else分支里分别用break、continue、pass控制复杂的逻辑流向。我第一次在实际项目里同时使用这三个的时候是一个数据处理脚本循环读取每一条记录遇到非法格式就continue跳过遇到结束标志就break退出遇到暂时不处理的类型就用pass占位等后续版本再补充。一套下来逻辑非常清晰也推荐新手多尝试这种组合的写法。4. 循环进阶enumerate、zip与推导式组合用法4.1 enumerate在遍历时同时拿到索引和值在Python中遍历列表时如果你不仅关心元素本身还需要它的下标位置很多人第一反应是写个计数器变量i 0 for name in students: print(i, name) i 1这种写法倒不算错但不够优雅而且容易忘记手动更新i导致索引错漏。用enumerate一行搞定for i, name in enumerate(students): print(i, name)enumerate同时返回索引和元素元组解包后直接分别赋值给i和name。还可以指定起始索引for i, name in enumerate(students, start1): print(i, name)这个写法在需要生成“第几个数据”信息时特别好用比如写日志、生成序号、按行处理文件时拿到的索引直接就能用。4.2 zip并行遍历多个序列有时候你有两个长度相等的列表想按位置对齐同时遍历比如一组姓名和一组分数names [张三, 李四, 王五] scores [90, 85, 88] for name, score in zip(names, scores): print(name, score)zip会按位置把多个序列的元素“打包”成一个个元组循环里解包后就可以分别使用。这个功能写起来非常顺手比起“用下标访问两个列表”的方式更简洁也不容易把下标写错。如果两个列表长度不一致zip会以短的序列为准多出来的元素会被丢弃。如果需要以长的为准可以用itertools.zip_longest来处理缺失部分。这算是zip耗时较久后才会踩到的深水区先记下即可。4.3 while循环下的zip等价操作其实自己用while也能模拟一个“并行遍历”的效果names [张三, 李四, 王五] scores [90, 85, 88] i 0 while i len(names) and i len(scores): print(names[i], scores[i]) i 1这个写法与zip的效果一致但代码明显更长。理解这个等价关系非常重要——它帮助你搞懂zip背后做的事情其实就是在维护索引按索引同时从几个序列取值。真正理解这个底层逻辑后你便会更欣赏zip这种优雅的封装。4.4 列表推导式很多循环可以简化成一行列表推导式说起来其实不是循环语句但它能替代很多简单的for循环场景。举个例子生成1到10的平方列表squares [x ** 2 for x in range(1, 11)]这一行代码等价于squares [] for x in range(1, 11): squares.append(x ** 2)列表推导式还支持条件过滤even_squares [x ** 2 for x in range(1, 11) if x % 2 0]这一行生成了2、4、6、8、10的平方列表。如果你之前只知道用for循环加if判断来写这个写法直接简化了一半以上的代码量。同样还有字典推导式squares_dict {x: x ** 2 for x in range(1, 6)}生成{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}。集合推导式、生成器推导式也存在不过初学者先掌握列表推导式后面遇到需要传入函数参数或做内存优化的时候再扩展学习就来得及。5. 循环里的经典坑死循环、误修改迭代变量与大循环性能5.1 死循环while里最容易踩的坑前面提过while循环里忘了更新循环变量会造成死循环。死循环的实际表现就是程序一直在跑光标一直闪界面卡住不结束。我在实际调试时也见过不少死循环案例除了忘记更新条件变量还有一类是将条件写反了导致永远满足。排查思路很有规律进入死循环先别慌直接在代码里插入print打印关键变量的值看它有没有在变化。比如i 1 while i 100: print(i) # 忘了写 i 1在循环体里打印i以后你马上就会发现i一直等于1条件永远为真。找到问题后再补上更新语句就好了。如果实在不好确认是哪一行导致的问题就疯狂加print一行一行缩小范围。这是排查死循环最朴素也最有效的方式。另一个经典错误写成导致条件恒真。比如while i 5:这行代码在Python里直接语法报错的不像C语言可以隐式赋值然后判断。Python在这点设计上比较安全不用太担心。但理论上仍可能出现赋值表达式walrus operator的场景初级内容不多展开只需要尽量少在条件里修改变量就好。5.2 循环中修改正在遍历的列表在for循环遍历列表时直接删除列表元素是另外一个很隐蔽的坑。看一个例子lst [1, 2, 3, 4, 5] for x in lst: if x % 2 0: lst.remove(x) print(lst)你可能预期结果只剩1、3、5但实际结果是[1, 3, 5]——运气好的时候是你要的结果但处理稍微复杂一点的列表时结果就会变得很奇怪。比如[1, 2, 3, 4, 6, 8]这种列表一边遍历一边删除会导致一些偶数被跳过。为什么会这样因为for循环在内部按索引位置迭代删除一个元素之后后面的元素会往前移动而循环的索引计数器不会自动后退于是就会跳过紧接着的下一个元素。解决办法有几种。最简单的做法是创建一个新列表把符合条件的元素收集到里面lst [1, 2, 3, 4, 5] new_lst [] for x in lst: if x % 2 ! 0: new_lst.append(x) print(new_lst)另一种做法是利用“遍历副本”的思路循环遍历列表的副本操作源列表这样索引就不会乱lst [1, 2, 3, 4, 5] for x in lst.copy(): if x % 2 0: lst.remove(x)推荐新手使用“新建列表收集结果”的方式逻辑最直白也不容易踩坑。等到编程熟练以后甚至可以直接改用列表推导式一行解决lst [x for x in lst if x % 2 ! 0]5.3 循环中修改变量值得注意的问题还有一类问题在for循环里擅自修改循环遍历的那个变量其实没什么用反而容易引起误导。比如for i in range(5): i 10 print(i)这段代码打印5个10但循环本身还是按原计划迭代5次下一次迭代时i重新赋值为序列里的下一个值。就算你能拿到赋值后的值也是临时的下一轮直接重置。所以修改循环变量本身几乎没有什么正面作用还会让读代码的人一头雾水。循环变量的意义是“当前取到的值”不要试图去改变它来控制循环走向等循环全部跑完之后变量会保留最后一次迭代的值这是正常的。基于这个原理还有一种更隐蔽的常见误区是用循环变量参与计算后想累计起来但忘记先声明一个普通变量来累加结果只保留最后一次的结果。比如total 0 for x in range(1, 101): total total x print(total)total在循环外部定义每次循环用total x更新总累加值循环结束后total就是1加到100的结果5050。如果把total 0放在循环里每次循环都会把total重置为0最后结果只有100。这类“循环变量的归属范围”问题新手经常搞混值得在练习时多花点时间理解。5.4 循环性能与不必要的计算说到循环性能先说结论Python的for循环本身并不慢关键在于循环体里做了什么。如果你在循环体里做大量无谓的重复计算或频繁调函数性能就会差很多。一个常见优化是把不随循环变化的计算提到循环外面。假设有一段代码需要循环遍历每个元素并累加math.sqrt的值它等效于直接sqrt每个值但如果循环外能先算的就不该在循环内每轮重算。比如闭区间里一个常量在循环里反复求一次幂倒不如把常量结果算好放在变量里循环里直接用这个变量。另外一个优化是减少在循环里对列表的append、字典的键查找次数多使用局部变量少使用全局变量这样能省下不少属性查找开销。不过这些优化属于性能调优范畴初学者暂时不需要过度关注先把逻辑写对等真的遇到性能瓶颈再考虑优化也不迟。过早优化在工程上是种浪费。话虽如此对“在循环里随手做大量无关计算”这种习惯还是要有意识地避免养成好习惯没必要等踩了坑再改。6. 新手的两个经典练习与完整实现6.1 打印九九乘法表嵌套循环的最佳入门练习九九乘法表几乎是所有Python学习者都会接触的练习因为它完美覆盖了嵌套循环、格式化输出、步长range这几个知识点。上面已经给出过最终的代码这里再复盘一遍完整的思考路径。我先想外层循环管行内层循环管列。第n行应该输出“n”的那一档乘法1 x n、2 x n一直到n x n。所以外层i从1到9内层j从1到i。每个乘法结果用制表符分隔同一行内不换行一行结束后换行。用字符串格式化控制输出格式对齐更美观for i in range(1, 10): for j in range(1, i 1): print(f{j} x {i} {i * j}, end\t) print()这段代码的每一行输出都经过format对齐end\t让结果在终端里排列得整整齐齐。我推荐新手上手循环时一定要亲自把这段代码敲一遍然后尝试改成打印倒三角版本条件改成j in range(i, 10)即可这个小的变换会让你对角标控制的理解上升一个台阶。6.2 猜数字游戏while交互式循环的完整练习猜数字游戏综合运用了while循环、break、input输入、条件判断好几块内容。我想给一套完整代码里面加一点简单的次数统计让练习更有意思import random secret random.randint(1, 100) count 0 print(我已经想好了一个1到100之间的数字来猜猜看吧) while True: guess int(input(请输入你猜的数字)) count 1 if guess secret: print(猜小了往大点猜) elif guess secret: print(猜大了往小点猜) else: print(f恭喜你猜对了你一共猜了{count}次) break这个程序的核心思路是先通过random.randint生成一个随机数然后进入while True循环接收用户的猜测判断大小给出提示直到用户猜中触发break退出循环。用户输入字符串时int转换有可能会报错那属于异常处理的内容还没学到的话可以先忽略这个练习的重点是通过边读边猜理解while True break的循环模式。6.3 用while循环实现计数器理解条件边界的实战很多教材会用“打印1到100”为例讲for循环却很少讲用while实现的版本。其实用while实现一遍计数器会极大加深你对循环两个关键词条件变化的理解i 1 while i 100: print(i) i 1这跟for i in range(1, 101): print(i)是等价效果。区别在于for循环的“步进”由range内部机制负责而while循环需要手动i 1。正是这个“手动”提醒了我们要思考循环的每个边界是怎么变化的条件什么时候会不成立。建议你把这个代码跑一遍然后改成打印100以内所有奇数、打印1到100的和、倒着打印50到1多练几个小变体循环的基本功就扎实了。7. 循环学习过程中的常见问题速查与实战吐槽7.1 为什么我的程序卡住了不输出任何东西多半是死循环。重点检查while的条件表达式确认循环体里是否更新了条件相关的变量如果是for循环检查是不是在遍历一个永远不会结束的迭代器。解决方式很有规律先在循环体里加打印语句确认变量的变化情况定位卡住的位置再把逻辑修正。还有一个常识IDE卡死不代表程序真的运行失败有时也可能只是输出太多刷屏导致界面没响应这种情况ctrlc中断程序就行。7.2 循环里用了break但还是没跳出循环极大可能是break写在嵌套循环的内层导致只跳出内层循环。上面说过break只能跳出最近的循环如果是嵌套循环要跳出全部可以考虑用返回或标志位。这里再补一个简洁的写法可以把逻辑包进函数在找到结果时直接从函数里return返回用“一级跳出全部”的方式取代多层break。def find_item(): for i in range(10): for j in range(10): if i * j 42: return i, j return None print(find_item())这个方法非常有用是我实际项目里最常用的“跳出多层嵌套”手段。7.3 循环里更新的变量在循环结束之后取什么值循环结束后循环变量会保留最后一次迭代的值。比如for i in range(5): pass print(i) # 输出4这是一个容易让人意外的点但它确实就是这么设计的。在while循环中循环变量的残留值也同样存在。这块如果没搞清楚后面在调试时看到“循环外用了循环变量”可能会丈二和尚摸不着头脑。7.4 无限循环到底有没有用有而且很有用。比如服务端监听、持续数据采集、实时信号处理都会用while True加上break的模式。设计数据库连接池的时候重试逻辑也会用while循环去不断尝试。担心死循环不丢人真正重要的是理解循环的终止条件是什么能用break、return等方式保证程序在正确的时间点退出。7.5 关于大循环性能我的建议如果你的循环要跑几十万上百万次优先考虑这几个方向一是尽量用局部变量减少循环体内的全局变量访问二是能用内建函数和库函数处理的逻辑尽量用现成的方法比如列表排序用sorted求和用sum不要手动写循环累计三是比较重的计算如果与循环无关提升到循环外四是真的遇到超大数据量考虑用NumPy等向量化方案替代纯Python循环。这些其实需要等学到后面才能完全消化现在先有个意识即可。我个人在实际操作中的体会是循环这种东西光看永远学不会必须亲手写、亲手调试、亲手踩坑才能真正形成肌肉记忆。你可以从今天开始试着把日常生活中的重复事务抽象成循环模型比如“统计这一周每天的运动步数”“逐个处理手机里的未读消息”这些场景然后在草稿纸上画出循环的过程图再翻译成Python代码。等for和while的边界、break和continue的作用、嵌套循环的顺序这些都亲自确认过一遍之后你写Python的底气会和之前完全不同。这个内容后续还可以这样扩展把循环语句和列表、字典结合再往后加上函数封装循环语句会渗透到你写的每一个Python项目里。先把这几个基础动作练好后面写代码的路会越走越顺。