
1. 先聊聊为什么循环是入门Python的第一道分水岭我自己带过不少新人发现一个很有意思的规律学变量、学数据类型大家进度都差不多但一讲到循环结构就开始有人掉队了。原因很简单——循环不是记住语法就行的知识点它要求你换一种思考方式从我要写每一步变成我要描述规则让计算机自己重复执行。你想一下如果要你打印100个数字你不会真的写100行print。你要告诉计算机的是从1开始每打印一个就加1直到超过100就停这就是循环。再比如处理一个Excel表格里的5000行数据写循环和不写循环工作量是天壤之别。这篇文章不打算把循环结构讲成教科书式的语法罗列我就站在一个实际写代码、踩过不少坑的人的角度把while、for、break、continue这些东西彻底掰开揉碎。顺便用搜索热度里那个while循环实现凯撒密码加密做一次完整实战代码可以直接抄走改改。不论你是刚装好Python、准备配置VSCode环境开始写第一行代码还是已经写过几天脚本但对循环总有种会用但讲不清的感觉这篇文章都适合你。看完之后不敢说让你成为循环大师但至少写循环的时候心里是踏实的。2. 循环前的热身先搞清楚两种循环的定位2.1 while循环只要条件成立就一直转while循环的核心逻辑就一句话先判断条件条件成立就执行执行完再回来判断直到条件不成立为止。count 0 while count 5: print(当前计数:, count) count 1这段代码的逻辑过程是第一次进来count0判断05成立打印count变成1再判断15成立打印count变成2一直重复直到count555不成立循环结束很多新人第一次看这段代码最想问的问题是为什么要写count 1不写会怎么样不写的话count永远是005永远成立程序就永远不退出。这就是传说中的死循环——你的程序卡在那里什么都不干就是不停转。这就像你让一个人一直往前走但没告诉他什么时候停下来他会走到天荒地老。2.2 for循环明确知道要遍历多少个元素for循环的逻辑是另一套从一个序列列表、字符串、range对象等里一个一个取出元素取完就结束。for i in range(5): print(当前是第, i, 次循环)同样是循环5次for循环看起来更简洁因为你不必手动维护计数器。这个别手动维护计数器的特点直接让for循环成为处理列表和字符串的首选。有人可能会问那while是不是多余的不是。for循环适合知道循环多少次或遍历已知序列的场景while循环适合不知道循环多少次只知道什么时候停的场景。举个例子写一个猜数字游戏用户输入一个数字程序判断是否猜对。你根本不知道用户要猜几次这时候for循环就不太合适了while就刚好因为你只关心没猜对就继续循环这个条件。3. while循环深度解析3.1 while的标准结构与控制流程while循环的标准语法比我上面写的稍微完整一点就两行while 条件表达式: 循环体流程图在我脑子里大概是这样的判断条件True/False→ True就进入循环体执行代码 → 执行完后回到判断 → False就直接跳过整个while块。这个环节最容易被忽略的地方是循环体里一定要有能让条件趋于False的代码比如改变计数器的值或者改变条件的输入。我见过很多新手写这样的代码# 错误示例 i 0 while i 10: print(i) # 忘了写 i 1运行这个代码程序会疯狂打印0你只能按CtrlC强制终止。在VSCode的终端里如果是Windows系统可能还要用CtrlBreak非常麻烦。3.2 我常用的while场景用户输入验证实际开发中while最常见的用途之一就是输入验证。比如你写一个程序要求用户输入一个1到100之间的分数用户可能输错很多次你不想每次输错都让程序崩溃这时候while就派上用场了score int(input(请输入分数1-100)) while score 1 or score 100: print(输入无效分数必须在1到100之间) score int(input(请重新输入分数1-100)) print(有效分数为, score)这里while的条件是score小于1或大于100意思就是只要输入不合法就一直重新输入直到合法为止。这个逻辑用for循环很不好写因为你不知道用户会错几次。注意一下这个场景里的一个关键细节为什么while前先要写一次input因为while第一次判断前必须有score的值如果连输入都没有条件没法判断。这种先取一个值再进入循环判断的模式在编程里很常见你以后还会在很多地方见到它。3.3 写while循环最常见的三个坑第一个坑刚才说了忘了更新循环变量导致死循环。第二个坑是条件边界写反或写错。比如想打印1到10你写的条件是while i 10结果只打印1到9。想要包含10应该写while i 10。这个等于号边界问题是所有语言初学者都会遇到的记住一个排查方法用程序会在条件刚好成立和条件刚好不成立的那两个值各走一遍。第三个坑比较隐蔽循环变量的初始值设置不当。比如你从0开始计数但实际数据是从1开始的最后结果总是少一个或多一个。我习惯写循环前先在草稿纸上推演一遍从初始值到循环结束的每个值变化都写出来确认没问题再动手。4. for循环与range()的搭配技巧4.1 range()函数的三种用法必须刻在脑子里range函数是for循环最常用的搭档它有三种标准用法# 用法1range(stop)从0到stop-1 for i in range(5): print(i) # 0 1 2 3 4 # 用法2range(start, stop)从start到stop-1 for i in range(2, 6): print(i) # 2 3 4 5 # 用法3range(start, stop, step)第三个参数控制步长 for i in range(1, 10, 2): print(i) # 1 3 5 7 9留意到没有range的结束值永远是取不到的。这是设计上的一致性Python里很多切片操作也都是含左不含右的规则。刚开始你可能觉得别扭但习惯之后会发现这个规则让很多代码的推导变得统一。4.2 for遍历字符串、列表、字典for的精髓不在于range在于遍历一切可迭代对象# 遍历字符串 for char in hello: print(char) # 依次输出 h e l l o # 遍历列表 fruits [苹果, 香蕉, 橙子] for fruit in fruits: print(fruit) # 遍历字典默认遍历键 scores {语文: 90, 数学: 85, 英语: 92} for subject in scores: print(subject, scores[subject]) # 遍历字典items for subject, score in scores.items(): print(subject, 的分数是, score)这几个例子合在一起其实是Python的一个核心思想不需要你费劲去算索引循环本身就会把每个元素递到你手上。新手可能一开始不习惯反而会写for i in range(len(fruits)): print(fruits[i])这代码没错但在Python里属于绕远路。尤其是在实际开发里这种写法可读性差团队协作时别人一眼看不出你要干什么。除非你真的需要用到索引位置否则直接for item in list才是Pythonic的方式。4.3 enumerate既要元素又要索引时的救命稻草说了除非需要索引那真的需要索引怎么办用enumerate函数fruits [苹果, 香蕉, 橙子] for index, fruit in enumerate(fruits): print(index, fruit) # 输出 # 0 苹果 # 1 香蕉 # 2 橙子这个函数会让循环体同时拿到第几个和元素本身。处理列表逐行输出时特别像打印一份带序号的名单实用性很高。实际项目里enumerate常用于调试时定位是哪一行数据出了问题你也可以在循环里配合条件判断只打印出满足某些条件的元素和它的索引效率比盲找快很多。5. break、continue与循环else给循环装个控制开关5.1 break条件满足就立刻终止整个循环有时候循环需要半路下车。比如寻找第一个能被7整除的数for i in range(1, 100): if i % 7 0: print(找到了, i) break # 找到第一个就停不再往下找break的执行效果是无论循环条件还成不成立循环体有多少行没执行直接跳出整个循环。这非常像你在一本书里翻目录找某一页找到就合上书不再翻后面了。我用break最多的场景是配合while做一个退出标志while True: command input(请输入命令输入q退出) if command q: break print(执行命令, command)这里while True本身是个死循环但因为循环体里有break所以死循环只是表面看起来死实际是会在满足一定条件后主动退出。这种模式在网络请求、事件处理里非常常见。5.2 continue跳过本次循环直接进入下一次continue的语义是本次循环到这里结束不再执行continue之后的代码回到循环开头执行下一次。for i in range(1, 11): if i % 2 0: continue # 偶数跳过不打印 print(i) # 只打印1 3 5 7 9这个场景你可能会想用ifnot的组合也能实现确实是。continue真正的价值在于当循环体有很多行代码而你只想在特定条件下跳过后面那些行。不用continue的话你得把后面所有代码都包进一个巨大的if里缩进一层又一层代码又丑又难改。我自己的一个习惯是如果continue和if/else都能实现优先选可读性更好的那种不为了用continue而用continue。如果后面的代码不多直接if条件判断更直白如果后面有十几行甚至几十行代码continue能帮你避免一次地狱级缩进。5.3 while/for后面的else没人告诉你的隐藏特性Python里循环后面可以跟一个else这个语法比较冷门但很有用——else里的代码在循环正常结束时执行如果是被break结束的else不执行。看例子for i in range(2, 10): if i % 3 0: print(f{i}能被3整除) break else: print(2到9之间没有能被3整除的数)这个代码会break掉所以else里的print不会输出。再换个例子for i in range(2, 5): if i % 10 0: break else: print(没有找到能被10整除的数)2、3、4都不能被10整除循环正常走完else里的内容就会输出。说实话这个特性一开始我也觉得鸡肋但在**查找失败**的场景里非常好用。比如在一堆数据里搜索关键词找到就break没找到就走else提示无结果。比用标志变量记录是否找到再判断要省好几行代码。6. 嵌套循环与性能意识6.1 嵌套循环的逻辑外层跑一次内层跑一整轮嵌套循环的本质是循环里套循环。外层每执行一次内层就要从头到尾跑一遍for i in range(1, 4): # 外层循环 for j in range(1, 4): # 内层循环 print(i, j)输出结果是9行因为外层3次内层每次3次3×39。这个乘法关系一定要想明白它是理解后续算法复杂度的起点。嵌套循环最常见的应用是打印乘法口诀表for i in range(1, 10): for j in range(1, i 1): print(f{j}×{i}{i*j}, end\t) print() # 外层每循环一次换一行注意这里内层循环的范围是range(1, i1)意思是第几行就打印几个式子。这就是靠外层循环的i值动态控制内层循环的次数。6.2 循环次数爆炸的隐患如果你有两层循环每一层都有1000次那么总循环次数就是1000×1000100万次。Python跑100万次不慢但如果里面还有复杂操作或者变成三层循环性能就会肉眼可见地下降。所以写嵌套循环时我几乎下意识会问自己三个问题内层循环能不能提前break能的话一定要加能不能把内层循环改成集合查找或字典查找很多双循环找交集的场景用集合能一行搞定数据量会不会很大如果会要不要考虑用numpy之类的库替代纯Python循环7. 完整实战用while循环实现凯撒密码加密7.1 什么是凯撒密码凯撒密码是一种非常古老的加密方式原理很简单把字母按照字母表顺序往后移动固定的位数。比如把字母表向右移动3位a变成db变成ec变成f以此类推。到了末尾的x、y、z就绕回开头变成a、b、c。明文字符串hello用密钥3加密后就变成khoor。解密就是反向移动知道密钥就能还原。7.2 核心思路拆解实现凯撒密码的难点不在于加密逻辑本身而在于处理字母的循环绕回。字母z加了3位不能变成ASCII表里的某个奇怪字符要绕回到c。我拆解一下思路先拿到每个字符的ASCII码Python内置函数ord比如ord(a)是97ord(z)是122给这个码加上密钥得到新码判断新码是否超过了122如果超过就减去26让它绕回a-z的范围用chr函数把新码转回字符但在动手写代码之前还要处理一个细节如果字符串里有大写字母、空格、标点符号怎么办7.3 用while循环实现既然这个实战项目点名要while循环我就全程用while来写不偷懒def caesar_encrypt(text, shift): result index 0 while index len(text): char text[index] # 只处理英文字母其他字符空格、标点、数字原样保留 if char.isalpha(): ascii_offset 65 if char.isupper() else 97 new_ascii (ord(char) - ascii_offset shift) % 26 ascii_offset result chr(new_ascii) else: result char index 1 return result plaintext input(请输入要加密的明文) key int(input(请输入加密密钥整数)) ciphertext caesar_encrypt(plaintext, key) print(加密结果, ciphertext)这里有个很关键的技巧(ord(char) - ascii_offset shift) % 26 ascii_offset。我给你拆解一下这个公式为什么这么写。假设字符是aascii_offset97shift3。计算过程是97减97等于00加3等于33对26取模还是33加97等于100100对应的字符就是d正确。再试zascii_offset97ord(z)122122减97等于2525加3等于2828对26取模等于22加97等于9999对应c。你看z成功绕回开头变成了c不需要用if去判断是否越界。取模运算%是处理循环绕回的第一选择相信我别用if硬判断代码又短又不容易出错。7.4 while中的计数器管理整个循环里最关键的是最后那个index 1。我故意把这三行代码拉开距离写是为了让你看清while循环里你用什么变量控制循环那个变量就一定要在合适的地方更新。在这里index从0开始每次处理一个字符就加1当index等于字符串长度时条件index len(text)不成立循环结束。和for循环的range自动管理计数器不同while的这个手动感反而让你更清楚地看到循环是怎么推进的。7.5 顺手加一个解密函数加密有了解密就是把shift换成负数其实同一个函数就能搞定def caesar_decrypt(text, shift): return caesar_encrypt(text, -shift) decrypted caesar_decrypt(ciphertext, key) print(解密结果, decrypted)因为加密是往右移动shift位解密就是往左移动shift位也就是往右移动-shift位。数学上完全对称所以一个函数直接复用。这个设计也提醒我写函数时尽量保持通用性不要写在某个场景里就绑死。7.6 运行效果演示我在VSCode终端里跑了一下这段程序请输入要加密的明文hello world 请输入加密密钥整数3 加密结果 khoor zruog 请输入要加密的明文Python is fun 请输入加密密钥整数5 加密结果 Udymts nx kzs第一行里h→k、e→h、l→o、l→o、o→r空格原样保留w→z、o→r、r→u、l→o、d→g。第二行里大写P→U其余小写字母分别移动5位空格保留。加密结果完全符合预期。这个项目如果让新人从头写卡壳的地方基本集中在两个一个是chr和ord这两个函数之前没用过另一个就是取模运算处理字母绕回这个思路。真明白之后凯撒密码就是个小玩具级别的程序但它能把while循环、字符串处理、条件判断、数学运算全部串起来所以我一直觉得它是很好的练手项目。8. 循环相关的常见问题排查速查表我在带人的过程中整理了一份循环相关的高频问题表新手按图索骥效率会高很多症状可能原因排查方法程序运行后卡住不动CPU占用高死循环循环变量未更新或条件永远为True检查循环体里有没有改变条件的语句临时在循环体里加print看执行情况循环次数比预期少一次边界条件用了但应该用推演边界值看程序是否会处理最后一个元素循环次数比预期多一次等于号边界处判断错误检查循环条件的小于号/大于号两侧是否应该包含等于只在最后一轮循环报IndexError列表索引越界循环内用的索引超出列表长度检查len和索引的关系range(len(list))是可以的但list[i1]要小心最后一位循环里有break但没生效break被写进了if的嵌套块外面检查break的缩进层级break必须和if在同一层级且在循环体内部修改列表时循环结果诡异在循环过程中删除或添加列表元素导致索引错乱不要在for循环中直接修改列表长度可以用列表推导式或先复制一份再处理函数里的while循环不退出return和break没有正确放置确认循环体内是否在条件满足时遇到了return或breakVSCode终端里循环输出太快看不清缺少延时或输出内容太多临时加import time; time.sleep(0.1)或者只print部分变量这张表里第一个死循环是出现频率最高的问题几乎每个初学循环的人都至少遇到一次。我的建议是先别急着删代码按CtrlC强行终止程序然后回去检查循环体里有没有让条件趋于False的语句。这个方法比开调试器还快。还有一个我踩过挺多次的坑是for循环内修改列表。举个最经典的例子你想删除一个列表里所有等于3的元素nums [1, 3, 2, 3, 4, 3] for num in nums: if num 3: nums.remove(num)运行完之后列表变成了[1, 2, 4, 3]最后一个3还在。原因是循环过程中列表的长度变了Python的for循环是从头到尾按索引推进的删除元素后列表元素整体前移循环索引却照样往后移动于是跳过了邻近的未处理元素。这种问题就不会出现在while循环里因为你用的是手动索引nums [1, 3, 2, 3, 4, 3] i 0 while i len(nums): if nums[i] 3: nums.pop(i) # 删除后索引不增加因为后面的元素已经前移了 else: i 1删除时i不增加这样才能保证每个元素都检查到。但说实话Python里处理这种需求更优雅的方式是列表推导式nums [num for num in nums if num ! 3]一行搞定而且完全避开索引问题。这也从侧面说明while循环的手动索引既是它的灵活之处也是它的风险来源。9. 调试循环的实用技巧与心得9.1 打印法调试最简单但别小看它我在实际调试循环时90%的情况是用print打印关键变量的值。不要觉得打印法低级它其实是理解循环执行过程最快的方式。当你不知道循环为什么多跑了一次时在循环开头打印一下计数器i 0 while i 5: print(f循环开始i {i}) if i 2: print(命中if分支i2) i 1 print(f循环结束i 更新为 {i})看输出里的i值变化你立刻就能定位问题出在哪一轮走的分支是什么。这比盯着代码脑内模拟高效得多尤其是嵌套循环时打印出外层第几次、内层第几次能帮你迅速定位循环层级之间的逻辑错误。9.2 VSCode断点调试当打印法不够用的时候当循环逻辑比较复杂或者你需要在特定条件下暂停检查所有变量时用断点调试就更合适了。VSCode里操作很简单在行号左侧点一下出现红点然后按F5启动调试程序运行到这一行就会停住。左侧面板可以看所有变量的当前值顶部的调试工具栏能控制继续单步跳过单步进入。调试循环时我最常用的两个调试操作是单步跳过和断点命中计数。单步就是一行一行地走观察循环变量的变化节奏断点命中计数是在红点上右键设置命中条件比如设置i 3时才停下来不用从第一个断点开始一步步按到第100次。9.3 在循环里写注释的习惯这个习惯是我被坑了几次之后才养成的。凡是写法上有隐含逻辑的循环我都会写一行注释说明循环的退出条件是什么while index len(text): # index逐个字符递增当index等于text长度时退出循环 # 异常字符原样保留避免破坏原始格式 ...注释不用长一两句话点明为什么这么写就够了。很多循环代码过了一个月自己也看不懂当时这个条件为什么要写而不是——有了注释回忆成本急剧下降。10. 最后分享几个让我编码效率翻倍的小习惯先聊循环里的命名习惯。我见过不少人写循环时用i、j、k但代码一复杂就分不清哪个对应哪个了。我现在写循环时会刻意用有语义的变量名遍历列表用item按索引遍历用idx处理的字符用char。代码读起来顺了很多出错也少了。再聊优先选择最自然的循环类型。我的经验是能确定循环次数就优先用for不确定或用标志位控制的场景再用while。不要嫌for看起来简单就强行用while也不必什么场景都套while True。让代码像它本来的样子是最舒服的。还有一个小技巧是把循环逻辑提取成函数。如果一个循环体内部超过十行我倾向把它单独写成函数循环只做驱动函数只做处理。这样测试循环时可以单独测函数不用每次跑完整段程序。说实话循环结构这个知识点学语法本身没有难度真正区分代码水平的是能不能在合适的场景用合适的循环以及能不能一眼看穿循环中的逻辑漏洞。我写这篇文章最想传达的不是某个语法有多重要而是希望你养成一种用循环表达重复规则的思维方式。凯撒密码的完整代码我已经放在上面了你可以直接复制动手跑一跑。改改密钥、试试中文和英文混排的字符串、或者把while改成for再看看效果都是很好的练习方向。循环这个东西光看永远觉得简单真上手写到报错、调通、跑出预期结果你才算真正学会。