有不少刚学会变量和分支判断的朋友,拿到一批数据时经常手足无措:你让他打印5个数字,他能老老实实复制粘贴五遍print;你让他处理100个订单,他的第一反应是复制粘贴一百遍。这其实不是懒,而是脑子里还没有“循环控制”这个概念。循环的意义不是帮你省几行代码,而是让同一段逻辑可以对任意规模的数据反复执行,并且每次执行时变量都能发生变化。
这一讲是Python基础系列的第三讲,专门讲循环控制语句。我会从for和while两种循环的基本写法讲起,再把break、continue、pass、循环的else子句这几个控制开关逐个拆开,最后用几个综合案例和常见坑位收尾。不管你是刚看完变量和if的新手,还是已经能写简单脚本但总觉得循环理解不透的人,这篇文章都适合你。
1. 为什么循环控制值得单独花一讲来聊
1.1 没有循环的世界:代码全是复制粘贴
可以先做个思想实验。假设你是班里的学习委员,需要把一份“期末成绩单”逐个打印出来:
print("A同学 成绩:85") print("B同学 成绩:92") print("C同学 成绩:78") print("D同学 成绩:64")只有四个学生,这么写还能忍。如果换成一整个年级八百个人呢?复制粘贴八百行print不但手累,更致命的是后续维护:一旦成绩单的格式想从“成绩:85”改成“综合测评:85”,你得手动改八百行,改漏一行就是线上事故。
循环解决的就是这个问题:把“对每个元素做什么”写一次,剩下的交给循环结构去重复执行。
students = ["A同学", "B同学", "C同学", "D同学"] scores = [85, 92, 78, 64] for i in range(len(students)): print(f"{students[i]} 成绩:{scores[i]}")这份代码的好处是:哪怕名单从4个人变成4000人,代码一行都不用改。
1.2 Python 里的循环只有两种,适用场景完全不同
循环控制的核心,其实就是回答两个问题:
- 我知不知道这段逻辑要重复多少次?
- 我知不知道什么条件下该停?
如果答案是“我知道要对一个固定集合里的每一项都处理一遍”,那是for循环的主场。比如遍历列表、字符串、字典、文件行。
如果答案是“我其实不确定要循环多少次,只知道某个条件为真时继续跑”,那就该用while循环。比如直到用户输入了q才退出、直到余额不足才停止取款。
用一个简单的表格感受一下区别:
| 场景 | 推荐循环 | 理由 |
|---|---|---|
| 把1到100的整数累加 | for | 次数固定,正好用range()生成 |
| 列出list里所有姓名 | for | 遍历集合元素,天然匹配迭代 |
| 等待用户输入直到输入q | while | 不知道用户要输入多少次 |
| 猜数字游戏,没猜中就继续 | while | 结束条件由玩家行为决定 |
| 打印九九乘法表 | for 嵌套 | 外层管行数,内层管列数,次数已知 |
记住这个判断逻辑比背语法重要得多。
2. for 循环:最常使用的“逐个处理”写法
2.1 range():左闭右开,这是新手最容易记反的地方
for循环最常见的搭档是range()。它生成一个整数范围,基础规则是“起始值包含,结束值不包含”。很多同学第一次写代码时想打印“1到5”,结果写成/``,发现出来的是0 1 2 3 4`,当场懵掉。
for i in range(5): print(i)输出:
0 1 2 3 4range(5)的含义是range(0, 5),也就是从0开始,一直数到4为止。这跟我给学编程的朋友打的比方一样:Python的range是个“包左不包右”的区间,右边界天生被排除在外。
想打印1到5,要写:
for i in range(1, 6): print(i)第三个参数是步长:
for i in range(1, 10, 2): print(i)输出1, 3, 5, 7, 9,这里步长设为2,相当于“每次跨两步”。步长还可以是负数,range(10, 0, -1)是从10数到1。
我个人的经验是:如果要在代码里表达“我要处理第1个到第10个”,直接写range(1, 11),千万要记得右边界加1。如果“我要处理前10个”,才写range(10)。
2.2 直接遍历字符串、列表、字典
range只是入门,for真正强大之处在于它可以直接拿到可迭代对象里的元素,省去手动数下标的操作。
遍历字符串时,一个字符一个字符取:
word = "Python" for ch in word: print(ch)遍历列表时,直接拿元素本身:
names = ["张三", "李四", "王五"] for name in names: print(name)遍历字典时,有好几种姿势。默认直接遍历拿到的是键:
scores = {"张三": 85, "李四": 92, "王五": 78} for key in scores: print(key, scores[key])如果既想要键又想要值,用.items():
for name, score in scores.items(): print(f"{name} 的分数是 {score}")这里有个细节值得展开:循环变量name和score的名字是自定义的,但顺序必须和.items()返回的“键、值”顺序一致。不少同学第一次写反成for score, name in scores.items(),数据就会错位,而且代码不报错,比较难排查。
2.3 enumerate():边遍历边拿序号
处理列表时,经常需要在循环里同时知道“当前是第几个”和“当前元素是什么”。初学者最朴素的做法是额外维护一个计数器:
index = 0 for name in names: print(index, name) index += 1这样写能跑,但多了一个需要手动维护的变量,一旦忘记在最后index += 1,序号就永远停在0。
更 Pythonic 的方式是enumerate():
for index, name in enumerate(names): print(index, name)默认序号从0开始。如果想让序号从1开始,给第二个参数:
for index, name in enumerate(names, start=1): print(f"第{index}名选手:{name}")enumerate的底层逻辑是:每次从列表里取一个元素,同时自动帮你把计数加一。我建议从学编程第一天起就适应这种写法,因为后面写项目时“序号+元素”成对出现的频率非常高。
3. while 循环:更自由,但也更容易写崩
3.1 基本结构与计数器套路
while的语法结构很直白:只要条件为真,循环体就一直执行。
求1加到100的和,不用公式,用循环暴力累加:
total = 0 i = 1 while i <= 100: total += i i += 1 print(total) # 5050这段代码的关键在第6行i += 1。你可能会问:这东西这么重要吗?重要。如果你忘记这行,i永远等于1,i <= 100永远为真,程序就会无限循环下去,CPU直接被这个死循环吃满。这类问题在初学阶段几乎人人都会遇到,我自己带过的同学里就有人在这个坑里卡了四十分钟才回过神来。
我把while循环的要素总结为三个:
- 初始条件:循环开始前变量要有确定的初始值。
- 结束条件:写在
while后面的判断表达式。 - 变化语句:循环体里必须改变参与条件判断的变量。
三个缺一不可。
3.2 什么时候该选 while
很多初学者容易陷入一个误区:所有循环都用for,因为for简单。但遇到“次数未知”的场景时,用for会非常别扭。
举个生活中的例子:模拟用户输密码,直到输入正确才能通过:
password = "123456" while True: user_input = input("请输入密码:") if user_input == password: print("密码正确,登录成功") break else: print("密码错误,请重试")这里while True是一个经典套路:先进入循环,再由循环体内的break决定什么时候退出。为什么不用for?因为你根本不知道用户会尝试几次,可能是1次,也可能是100次。用while True + break是表达“不确定次数、直到某件事发生”最自然的方式。
再比如余额不足就不再取款的场景:
balance = 1000 while balance >= 100: withdraw = 100 balance -= withdraw print(f"取款100元,余额{balance}") print("余额不足,停止取款")这段代码会不断取款,直到余额跌破100为止。注意,这种写法里循环次数取决于balance的变化,只有while才能表达这种“动态终止条件”。
我在实际教学里发现一个很实用的判断技巧:先把问题的描述大声读一遍。如果描述里有“直到……”“当……的时候继续”,那就用while;如果描述里有“对每一个……”“依次……”,那就用for。
4. break / continue / pass / else:循环的四个“控制开关”
4.1 break:直接跳出整层循环
break的作用是“立即终止当前这层循环”,循环体剩余代码不再执行。它非常适合“提前结束搜索”的场景。
假设要从名单里找到第一个姓“张”的人,找到了就直接停止:
names = ["李四", "王五", "张三", "赵六", "张三丰"] for name in names: print(f"正在检查 {name}") if name.startswith("张"): print(f"找到了:{name}") break运行结果:
正在检查 李四 正在检查 王五 正在检查 张三 找到了:张三注意,程序只检查到“张三”就停了,“赵六”和“张三丰”不会再被遍历。这种提前退出的机制在数据量大的场景里能省下大量时间。比如在一个十万行的日志文件里找第一个错误线索,找到即停,比把十万行全读完再判断要高效得多。
4.2 continue:跳过本轮,继续下一次循环
continue和break经常被混在一起,但二者方向完全相反:break是结束整个循环,continue是结束“当前这一轮”,直接进入下一轮。
经典的例子:只打印1到10之间的奇数:
for i in range(1, 11): if i % 2 == 0: continue print(i)当i是偶数时,continue触发,后面的print(i)不执行,程序直接进到下一个i。所以输出是1 3 5 7 9。
continue在实际项目中经常用来“跳过脏数据”。比如处理一批用户信息时,遇到年龄字段为空的数据,直接跳过,不要参与后续的统计逻辑:
users = [ {"name": "A同学", "age": 20}, {"name": "B同学", "age": None}, {"name": "C同学", "age": 22}, ] for user in users: if user["age"] is None: print(f"{user['name']} 数据缺失,跳过") continue print(f"{user['name']} 的年龄是 {user['age']}")有人可能会问:“那我用if...else不也一样吗?”确实能做到,但continue可以减少一层嵌套,让代码更扁平。嵌套层级越深,读代码的人脑负担越重,能用continue提前过滤掉异常分支是好习惯。
4.3 pass:占位符,什么都不做
pass可能是四个“控制开关”里最容易被忽略的。它不控制循环流程,它只是“什么都不干”的占位语句。
什么时候需要它?当你已经想好循环的结构,但内部逻辑还没写完时:
for user in users: if user["age"] is None: pass # 暂时不处理,等会再写 else: print(user["name"])没有pass的话,if分支下面必须有一句代码,否则直接语法报错。所以pass更像是一个“开发中的占位符”,告诉 Python“这里我先留空,别报错”。
4.4 for/else 和 while/else:循环正常结束才走 else 分支
这个特性是Python里一个不大众但很好用的语法:for循环和while循环后面都可以跟一个else,但它的触发条件是“循环没被break提前退出”。
光看描述可能觉得绕,举个质数判断的例子就清楚了。判断一个数是不是质数,常规思路是写一个标志位:
num = 17 is_prime = True for i in range(2, num): if num % i == 0: is_prime = False break if is_prime: print(f"{num} 是质数") else: print(f"{num} 不是质数")用for...else可以省掉标志位:
num = 17 for i in range(2, num): if num % i == 0: print(f"{num} 不是质数,能被 {i} 整除") break else: print(f"{num} 是质数")逻辑非常好理解:如果循环过程中触发了break,说明“能整除”的情况发生了,不需要走else;如果整个循环走完都没触发break,说明从2到num-1都没有任何数能整除它,这时候才走else,判定它是质数。
我把四个关键字的核心差异整理成一张表,方便你复习:
| 关键字 | 作用 | 适用场景 | 是否终止循环 |
|---|---|---|---|
break | 立即结束当前整个循环 | 找到了目标,无需继续搜索 | 是 |
continue | 跳过本轮,立刻进入下一轮 | 某个数据不合规,跳过不处理 | 否 |
pass | 占位,什么都不做 | 逻辑还没写,先占个位置 | 否 |
else(循环后可加) | 循环未被 break 中断时执行 | 质数判断、全部检查通过后的处理 | 否 |
4.5 嵌套循环下的 break:只退出一层
很多同学刚开始用break时会误以为它能一次性结束所有循环。实际上,break只作用于它所在的那一层循环。
for i in range(3): for j in range(3): print(i, j) if j == 1: break这段代码里,内层循环在j == 1时中断,但外层循环不受影响,继续跑下一个i。输出:
0 0 0 1 1 0 1 1 2 0 2 1如果你想从嵌套循环的最内层一下子跳出所有层,一种常见做法是加一个标志位,每层循环都检查这个标志。但这个话题偏进阶,初学阶段先记住“break 只跳一层”就够用了,免得在代码里做出与实际意图不符的假设。
5. 循环嵌套与综合案例:把前面的知识点串起来
5.1 循环嵌套的执行顺序
循环是可以套循环的。执行逻辑一句话总结:外层循环每跑一次,内层循环从头到尾完整跑一遍。
理解这个顺序,可以做一个最直观的练习:打印“第几轮”和“第几个”。
for i in range(1, 4): for j in range(1, 3): print(f"外圈 i={i},内圈 j={j}")输出顺序:
外圈 i=1,内圈 j=1 外圈 i=1,内圈 j=2 外圈 i=2,内圈 j=1 外圈 i=2,内圈 j=2 外圈 i=3,内圈 j=1 外圈 i=3,内圈 j=2可以看到,i=1的时候,j把1、2都跑了一遍;然后才轮到i=2。这种“外层控行、内层控列”的模式,是后面打印表格、图像处理的基础。
5.2 综合案例1:打印九九乘法表
九九乘法表是学习嵌套循环最经典的练习,没有之一。需求明确:9行,第i行有i列,每个单元格是行号 × 列号 = 结果。
for i in range(1, 10): for j in range(1, i + 1): print(f"{j} × {i} = {i * j}", end="\t") print()核心点有两个:
- 内层循环的范围是
range(1, i + 1),这样保证每一行只输出到“该行行号”为止。比如第3行,j从1到3。 print(f"...", end="\t")让每个乘法式之间用制表符分隔,不换行。内层循环结束后,再单独调用print()来换行。
输出效果:
1 × 1 = 1 1 × 2 = 2 2 × 2 = 4 1 × 3 = 3 2 × 3 = 6 3 × 3 = 9 ...这段代码如果自己写一遍,你对循环嵌套的理解会上一个台阶。我建议新手不要只看,手动敲一遍,然后试着改动end="\t"为end=" ",看看排版差异,理解end参数对输出格式的影响。
5.3 综合案例2:找出 2 到 100 之间的所有质数
之前用for...else判断了单个数字是不是质数,现在把它放进一个循环里,批量做统计和输出:
prime_numbers = [] for num in range(2, 101): for i in range(2, num): if num % i == 0: break else: prime_numbers.append(num) print(prime_numbers)这其实是一个“嵌套循环 +for...else+ 列表收集结果”的综合应用。外层循环逐个检查2到100,内层循环逐个尝试除法,一旦发现能整除就break,如果完整跑完内层循环没有发现任何因子,就说明num是质数,进入else分支把它收集起来。
跑完之后,prime_numbers就是2到100之内的所有质数列表。你可以打印长度确认一下,完整的2到100质数一共25个。
6. 新手最容易踩的四个循环坑
6.1 坑1:忘记更新循环变量,结果是死循环
前文已经提过,while循环里如果条件永远为真,程序就永远不会结束。这是个“运行时问题”,没有语法报错,程序也不会直接崩,只会卡在那一动不动,CPU占用率还会居高不下。
最常见的翻车写法:
i = 1 while i <= 5: print(i) # 忘了写 i += 1运行这条代码,终端会疯狂输出1,永远停不下来。遇到这种情况,只能强制中断程序(在多数编辑器和终端里按Ctrl+C)。其实这也是练习while时的必修课:多踩一次这个坑,以后就长记性了。
6.2 坑2:一边遍历列表一边删除元素
初学阶段很多人会写出这样的代码:想把列表中所有偶数删掉。
numbers = [1, 2, 3, 4, 5, 6] for num in numbers: if num % 2 == 0: numbers.remove(num) print(numbers)你可能会以为会得到[1, 3, 5],但实际上输出是[1, 3, 5]吗?不同Python版本下结果可能不同。不展开说要害了,本质上是因为一边遍历一边修改列表的长度,导致遍历“指针”跳过了一个元素。
处理这种需求,我推荐一个稳妥套路:不要边遍历边改原来的列表,而是用一个新的空列表来收集符合条件的元素,或者先拷贝一份再遍历原列表做删除判断。
numbers = [1, 2, 3, 4, 5, 6] result = [] for num in numbers: if num % 2 != 0: result.append(num) print(result)这属于“构造新列表代替原位修改”的思路,清晰且不容易出错。
6.3 坑3:break只跳出当前一层,却以为整个循环都停了
这个坑在嵌套循环时十分常见。前面已经讲过,break只作用于当前层。如果三层循环嵌套,内层break只跳出最内层,外面两层照样执行。想实现“所有层都停”,需要更复杂的标志位设计,但对于基础阶段的读者,先把“每层管每层”记清楚。
6.4 坑4:空循环的迷惑行为
range(0)生成的序列一个元素都没有:
for i in range(0): print("这行永远不会打印")这在语法上合法,但循环体一次都不会执行。同理,range(5, 5)也是空区间,因为左边等于右边,而区间是“左闭右开”。很多同学第一次发现这段代码不输出任何东西时,还以为是程序坏了,其实这是非常正常的空循环行为。如果业务逻辑里需要“零数据时不报错”,这种特性反而有用。
6.5 配套练习建议
基础语法看完之后,光看不练等于白学。我在带人学Python时,一般会让对方做两件事:
- 自己独立写一遍九九乘法表,要求不查资料,然后试着把输出对齐成三角形。
- 写一个“三次密码尝试”小程序:给用户3次机会输入密码,三次都错就提示“尝试次数用尽,账号锁定”,使用
for配合break实现。
第二个练习看起来简单,但能把for、if、break、range、input这几个基础点全部串在一起。写完后,试着把“三次机会固定”改成“无限次但每错一次提示剩余次数”,就又变成了while的场景。两个循环你都能随手切换,就算真正入门了。
我个人的体会是,循环控制的难点从来不是语法,而是“把一个重复动作翻译成循环条件”的思维。今天看懂了break和continue的区别,明天遇到复杂业务时还要能判断到底该用for还是while。多写多练,比抱着教程反复看有用得多。