ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python循环语句与异常处理详解:for、while及try-except实战指南

2026/9/16 4:33:25 拓冰建站 浏览量
Python循环语句与异常处理详解:for、while及try-except实战指南 写循环语句这块的时候我脑子里第一个蹦出来的场景是去年接的一个自动化小需求把几十个Excel成绩单逐行读出来、按班级汇总、再生成排名。落到Python里核心其实就是for循环遍历文件、字典存数据再配一点异常处理兜底。有朋友可能已经猜到了程序控制语句有三大块顺序、分支、循环。上一篇我们已经把分支判断——if-elif-else——聊完了今天这篇是程序控制语句的第二部分重点拆解for循环、while循环外加异常处理语句。为什么要把异常处理和循环放在一起讲因为你在循环里写业务逻辑最容易出事的点就是数据类型转换失败、下标越界、文件不存在没有异常处理兜底循环写得再漂亮也经不住真实数据一锤。不管你是刚装好Python的新手还是写过一阵子但没系统梳理过循环的朋友这篇文章都值得你花几分钟看完。我会把循环的选型思路、常见坑点、实际项目里的处理习惯都讲一遍代码可以直接抄思路可以带走。1. 循环语句的总体设计与思路拆解1.1 为什么循环是程序里最实在的“省力工具”先说一个最朴素的道理计算机最擅长的事情就是“重复”。人做重复的事情会累、会出错、会烦但机器不会。循环语句就是把你“想重复做的事情”明确告诉计算机的语法工具。举个例子。你手上有50份作业要逐个登记分数。人力操作大概是拿起一份、翻开、写分、放下去然后重复50次。程序里如果不用循环就得老老实实写50行print和人力登记一样笨。用for循环scores [85, 92, 78, 96, 88] for score in scores: print(当前分数是, score)三行代码搞定。数据量从50变成5000代码一行都不用改这是循环最核心的价值用一次代码编写换无限次自动执行。在Python里完整的程序控制语句包含四种结构顺序结构、分支结构、循环结构、异常处理结构。顺序结构是代码按行往下走不需要额外语法分支结构用if-elif-else在岔路口选一条路循环结构用for和while让一段代码反复执行异常处理用try-except在程序出错时接管流程而不是让程序直接崩溃。这四者组合起来你就能写出现实世界中绝大多数业务逻辑了。1.2 写循环前必须想清的三个问题初始化、条件、迭代我见过不少新手写循环上来就敲代码结果一半时间花在debug上。实际上任何循环不管用for还是while写之前想清楚三件事就够了从哪开始初始化什么时候停终止条件每跑一轮怎么变迭代更新这三件事有个很形象的比喻长跑。初始化是起跑线终止条件是终点线迭代更新是你每圈的步伐调整。拿最简单的“1加到100”来说。用for循环初始化由range隐式提供终止条件和迭代更新也由range自动完成total 0 for i in range(1, 101): total i print(total) # 5050用while循环三要素就要自己显式写出来total 0 i 1 # 初始化从1开始 while i 100: # 终止条件小于等于100 total i # 循环体 i 1 # 迭代更新每轮1 print(total) # 5050注意看while版本里如果漏掉最后一行i 1程序就会一直跑下去因为i永远停在1永远满足i 100这就是死循环。相比之下for循环把更新动作封装在range机制里所以用for写简单的数字循环更不容易出问题。1.3 for和while怎么选一个实用的选型决策表很多新手会纠结for和while功能上不是一样吗到底该用哪个我的判断标准就一句话循环次数在进入循环前是否明确。如果明确知道要循环多少次比如遍历一个列表、读固定行数的文件、生成1到10的数字优先用for。如果不知道什么时候停取决于某个条件是否成立比如等待用户输入正确密码、一直读到文件末尾、不断请求接口直到拿到可用结果就用while。判断维度for循环while循环循环次数进入前明确进入前不明确典型对象列表、字符串、range、字典计数器、输入校验、流式读取迭代更新自动处理手动处理最大风险嵌套过深导致性能问题死循环适用场景遍历和批量处理条件驱动型场景表格记不住也没关系我在实际项目里还有一个“懒人经验”优先用for遇到“真不知道要循环多少次”的情况才换while。大多数遍历类需求用for都够用且更安全。2. for循环遍历一切可迭代对象2.1 for基础语法与range函数的4种常见姿势for循环的基本语法结构非常简单for 变量 in 可迭代对象: 循环体这个“可迭代对象”可以是一串数字、一个字符串、一个列表、一个元组、一个字典的key、一个集合甚至是一个文件对象。只要能被逐个取出来for就能遍历它。我先把range函数的几种姿势列全这是写数字循环最常用的函数# 姿势一range(停止值)从0开始到停止值-1 list(range(5)) # [0, 1, 2, 3, 4] # 姿势二range(起始, 停止)不包含停止值 list(range(2, 8)) # [2, 3, 4, 5, 6, 7] # 姿势三range(起始, 停止, 步长)步长为2 list(range(1, 10, 2)) # [1, 3, 5, 7, 9] # 姿势四负步长实现倒序 list(range(10, 0, -2)) # [10, 8, 6, 4, 2]注意一个非常容易踩的坑range的停止值是“不包含”的。很多人写range(1, 101)以为包含101实际上生成的是1到100。想包含右侧边界就必须把停止值加1。这个坑我见过至少十个人踩过包括我自己刚学的那会儿所以专门拎出来强调一下。2.2 遍历字符串、列表、字典、集合的操作细节无数人学完基础语法后卡在数据结构上其实for循环就是打通数据结构的钥匙。逐个来看。遍历字符串拿到的是每个字符for ch in Python: print(ch) # P y t h o n遍历列表拿到的是每个元素fruits [苹果, 香蕉, 橙子] for fruit in fruits: print(fruit)遍历字典分三种姿势。默认直接遍历拿的是key用items()拿键值对用values()拿值student {name: 小明, age: 18, class: 高二3班} for key in student: print(key) # name age class for key, value in student.items(): print(key, value) # 键和值都能拿到 for value in student.values(): print(value) # 小明 18 高二3班遍历集合set时元素顺序是不保证的这点要心里有数for num in {3, 1, 2}: print(num) # 输出顺序可能是 1 2 3但不要依赖这个顺序在我实际处理数据分析任务时最常用的其实是“遍历字典的items”因为很多数据都是嵌套结构比如“班级 - 学生 - 成绩”三层遍历用好了数据清洗基本没什么压力。2.3 enumerate和zip给遍历加两个顺手工具很多语言里遍历索引需要手动访问下标Python提供了更优雅的enumerate和zip。enumerate的作用是同时拿到“索引”和“元素”这在需要知道元素位置时非常有用courses [Python, Java, Go] for index, course in enumerate(courses, start1): print(第, index, 门课是, course)输出第 1 门课是 Python 第 2 门课是 Java 第 3 门课是 Gozip的作用是把多个列表按位置“打包”成对names [张三, 李四, 王五] scores [88, 95, 76] for name, score in zip(names, scores): print(name, 的成绩是, score)zip有一个隐藏特性它会按最短的那个列表截断。比如names有5个元素scores只有3个zip只处理前3对。这个在很多场景下是好事但如果没意识到数据可能会悄悄“丢”掉所以用到zip时最好确认一下两个列表长度是否一致。2.4 break、continue、else三种控制循环节奏的关键字买了车之后你不仅需要知道怎么踩油门还得知道怎么刹车。for循环里的break、continue、else就是干这个的。break是立即结束整个循环。典型场景在列表中找第一个满足条件的元素找到就停numbers [5, 12, 7, 30, 8] for num in numbers: if num 10: print(第一个大于10的数字是, num) breakcontinue是跳过本轮循环进入下一轮。典型场景跳过不需要处理的数据for num in range(1, 10): if num % 2 0: continue # 偶数跳过不打印 print(num) # 只打印奇数else是和break配合使用的“晴雨表”循环完整跑完、没有触发break时才执行else里的代码。这个写法特别适合“查找确认找到与否”的场景numbers [1, 3, 5, 7] for num in numbers: if num 4: print(找到了4) break else: print(循环结束都没有找到4)这个else如果放在循环外用普通布尔变量也能实现但代码又臭又长。for...else是Python的语法糖用起来不仅省变量代码意图也更清楚。2.5 嵌套循环和列表推导式嵌套循环就是循环里面套循环。最典型的场景是打印九九乘法表for i in range(1, 10): for j in range(1, i 1): print(f{j} * {i} {i * j}, end\t) print()嵌套循环本身不难难的是嵌套层次一旦超过3层代码可读性会急剧下降执行效率也会因为乘法增长而变差。比如两层各循环1000次就是100万次操作还能接受三层就是10亿次很多场景已经跑不动了。所以嵌套循环尽量控制在两层以内超过两层要考虑拆分函数或用其他结构。从for循环还可以延伸出一个很实用的语法列表推导式。它是for循环的“压缩版”一行代码生成新列表squares [x * x for x in range(1, 6)] print(squares) # [1, 4, 9, 16, 25]列表推导式本质就是把“构建结果列表”和“遍历”合并到一起。初学阶段可以先用常规for循环写熟练之后再改成推导式两种写法等价后者更简洁但可读性对新人不够友好。我自己的习惯是逻辑简单的用推导式逻辑复杂的用普通for绝不为省一行代码牺牲可读性。3. while循环条件驱动与死循环防治3.1 基本结构与典型场景while的语法比for还要直白只要条件为True就一直执行循环体。while 条件判断: 循环体什么时候该用while我再补充几个具体场景比抽象描述更好理解验证用户输入是否合法因为不知道用户会输几次爬虫里不断翻页直到没有下一页监控脚本里每隔几秒检查一次系统状态游戏主循环里等待玩家退出命令。这些场景的共同点是循环次数完全取决于外部条件而不是一个确定的序列。举个例子处理一个“直到用户输入正确才放行”的场景password while password ! python123: password input(请输入密码) print(密码正确欢迎进入)这个循环你没法提前预测用户会输多少次可能一次成功可能试五次所以用while比for自然得多。3.2 死循环的两种标准防治套路计数器与哨兵值while最大的风险就是死循环。程序跑起来一直不结束CPU占用率飙到100%最后只能强制终止。我总结了两套最标准的防治套路。第一套计数器。用于已知循环次数的场景。count 0 while count 5: print(第, count 1, 次执行) count 1第二套哨兵值。用一个“特殊最终值”标记结束时机。典型哨兵就是输入某个特定字符就结束s input(请输入内容输入quit退出) while s ! quit: print(你输入了, s) s input(请输入内容输入quit退出)实际开发中还有一类死循环是“故意为之”比如服务器的主循环要求永远跑下去while True: # 接收请求、处理请求 pass这种写法本身没错但要确保循环体内部有break或return的出口否则一旦进入程序就真的“无限循环”了。开发时强烈建议在关键分支打日志否则排查问题的时候你会头皮发麻。如果你不小心跑出了死循环在终端里按 CtrlC 可以强制终止Python程序但不要把这个当习惯真正要做的是搞清楚退出条件为什么没满足。3.3 实战用while循环实现凯撒密码加密热搜词里一直有“while循环实现凯撒密码加密”这个需求确实挺经典。凯撒密码的原理很简单把字母按字母表平移固定位数。比如shift3a变成db变成ex变成ay变成b。关键点是落点要控制在字母表范围内所以要用取模运算。我分别用while和for各写一版方便你对比两种写法的差异。先用while实现遍历字符串用索引控制def caesar_encrypt(text, shift): result [] i 0 while i len(text): ch text[i] if a ch z: offset ord(a) new_ch chr((ord(ch) - offset shift) % 26 offset) result.append(new_ch) elif A ch Z: offset ord(A) new_ch chr((ord(ch) - offset shift) % 26 offset) result.append(new_ch) else: result.append(ch) i 1 return .join(result) print(caesar_encrypt(Hello, World!, 3))再看for版本def caesar_encrypt(text, shift): result [] for ch in text: if a ch z: offset ord(a) new_ch chr((ord(ch) - offset shift) % 26 offset) result.append(new_ch) elif A ch Z: offset ord(A) new_ch chr((ord(ch) - offset shift) % 26 offset) result.append(new_ch) else: result.append(ch) return .join(result)看到区别没有while版本需要自己维护i并在循环体末尾手动i 1for版本直接遍历字符串压根不需要索引。光从这段代码对比你就能理解为什么我推荐大多数场景优先用for少写一行i更新就少一个忘记更新导致死循环的隐患。如果想解密把shift改成负数比如-3即可过程完全对称。3.4 while...else没进循环也要有收尾方案和for...else类似while也有else分支它的含义是循环条件变为False、正常结束时执行一次else里的代码如果循环是被break终止的则不执行。attempt 0 while attempt 3: value input(请输入一个数字) if value.isdigit(): print(合法输入退出循环) break attempt 1 else: print(3次输入都不合法)这个写法非常适合“给用户N次机会”的逻辑三次内成功就break退出else里的提示不会执行三次都失败循环正常结束else提示次数用尽。它比在外面再判断一次attempt变量简洁得多。3.5 实际场景选型清单根据实际项目经验我把常见场景直接列成清单你以后遇到类似需求直接对照场景推荐原因遍历列表/字典/文件行for遍历已知集合数字累加或生成序列forrange自动管理迭代用户输入校验/登录重试while次数未知或看情况不断翻页抓取数据while依赖“是否有下一页”服务常驻监听while True主要靠break退出递归式搜索树/图递归for每层结构相同4. 异常处理语句让程序出错时不崩也不慌4.1 为什么需要异常处理程序出错是常态不是意外写完循环再聊Python里另一个极其重要的程序控制能力——异常处理。很多人刚开始写程序总觉得“代码写对就不会出错”。真实情况恰恰相反程序里最容易出错的就是和外部打交道的地方——用户的输入可能是乱写的、文件可能不存在、网络可能超时、磁盘可能满了。这些都不是你的代码逻辑错了而是运行环境的不确定性导致的。异常处理语句就是为这种不确定性准备的“兜底方案”。打个比方正常代码是主干道异常处理就是沿途的应急通道。主干道没堵的时候应急通道不参与一旦前方塌方异常处理立刻接管绕开故障区保证程序还能继续往前走。异常处理的基本结构长这样try: # 可能出错的代码 num int(input(请输入一个整数)) print(你输入的是, num) except ValueError: # 出错后执行的兜底代码 print(输入不合法不是整数)这段代码的意思先尝试执行try块里的代码如果一切正常except根本不会执行如果某一行抛出ValueError异常程序立刻跳到except分支执行兜底逻辑然后继续往下走不会崩溃。4.2 异常传播机制与多个except的匹配顺序异常有一个非常重要的机制传播。假设在循环里调用了一个函数函数内部又调用了另一个函数最内层抛出的异常会一层一层往外抛直到某个except接住它如果所有层都没有接管程序就崩了同时打印一大段Traceback。def inner(): return 10 / 0 def outer(): inner() try: outer() except ZeroDivisionError: print(捕获到了除零异常)异常会从inner一路冒到最外层的try被except ZeroDivisionError接住这就是异常处理的完整链路。实际开发中一段try可能面对多种异常Python允许跟多个excepttry: value int(input(请输入数字)) result 100 / value print(result) except ValueError: print(这不是数字) except ZeroDivisionError: print(不能除0)这里有几个顺序上的讲究。第一多个except是自上而下匹配的先检查ValueError再检查ZeroDivisionError。第二如果某个except写的是父类它会“吞掉”子类异常所以父类要放在子类后面。比如Exception是大多数异常的父类放在最前面会把后续的except全部架空try: result 100 / 0 except Exception: print(出错了) # 这个分支会接住 except ZeroDivisionError: print(除0了) # 永远不会执行写反了看起来不报错但后面的代码成了摆设。我写多异常处理时习惯把“具体异常”写在前面“通用异常”放最后兜底。前面说的裸except意思是不写异常类型的except:它能捕获包括SystemExit、KeyboardInterrupt在内的所有异常连CtrlC都拦下来这对程序行为影响很大不建议在生产代码里用。真要兜底用except Exception即可。4.3 常见异常类型对照表Python内置了几十种异常但日常开发高频出现的其实就那几种。我列个表异常类型触发场景一句话说明ValueErrorint(abc)数据值不合法TypeErrorabc 123类型不匹配IndexErrorlst[10]列表下标越界KeyErrord[缺的键]字典键不存在ZeroDivisionError10 / 0除数为0FileNotFoundErroropen(不存在.txt)文件没找到PermissionError打开无权限文件权限不足UnicodeDecodeError读文件编码不对编码解析失败AttributeErrorobj.name不存在对象没有该属性网上有人把Linux里“err:23 数据错误(循环冗余检查)”和Python异常混为一谈其实那是磁盘或光驱数据读取时的硬件校验问题和Python这个异常表不是一回事别搞混了。真正的FileNotFoundError、PermissionError在处理文件操作时更常见。4.4 else、finally与raise的完整使用说明除了try和except异常处理语句还有三个常用关键字else、finally、raise。else的用法和循环里的else有些类似try块执行正常、没有触发任何异常时执行else分支。它的价值在于“把主逻辑和异常逻辑分开”代码层次更清楚try: num int(input(请输入一个整数)) except ValueError: print(输入不合法) else: print(合法输入平方是, num * num)finally是无论有没有异常都一定会执行的代码块。它最典型的使用场景是“收尾清理”关闭文件、关闭数据库连接、释放资源。比如f open(data.txt, r, encodingutf-8) try: content f.read() print(content) except FileNotFoundError: print(文件不存在) finally: f.close()这里我故意把open写在try外面是因为如果文件本身就不存在变量f根本没有被创建finally里再执行f.close()反而会抛出NameError。把open放外面配合finally关闭是比较稳妥的经典写法。不过现代Python更推荐用with open语句它内部就实现了上下文管理和自动关闭完全省掉finallywith open(data.txt, r, encodingutf-8) as f: content f.read() print(content)两者对比with写法更短、更不容易出错而且文件一定会在离开with代码块时关闭无论里面发生了什么事。raise是主动抛出异常。它很有用但经常被忽视。什么时候需要主动抛典型场景你写了一个函数接收的参数不合理就应该主动抛异常让调用方知道问题而不是默默返回一个奇怪的结果糊弄过去def set_age(age): if age 0 or age 150: raise ValueError(年龄必须在0到150之间) print(年龄设置为, age) set_age(-5) # 抛 ValueError程序不会静默继续这种“快速失败”的思想很关键把问题尽早暴露出来比带着错误数据继续跑要好得多。4.5 进阶自定义异常与工程级异常处理规范内置异常覆盖不了所有业务场景时可以自己定义异常。做法很简单继承Exception类class PasswordTooShortError(Exception): pass def check_password(pwd): if len(pwd) 6: raise PasswordTooShortError(密码长度不能少于6位) return True try: check_password(123) except PasswordTooShortError as e: print(密码校验失败, e)自定义异常的好处是错误信息可以携带非常明确的业务语义。别人读代码时看到except PasswordTooShortError就知道这里在单独处理“密码太短”这个业务情况而不是一个模糊的Exception。工程上还有两个处理规范我个人比较坚持。第一try的包围范围“宜小不宜大”。让try只包住最可能出错的那几行比如int转换、网络请求、文件读取而不是把整个函数几百行代码全丢进try里。范围越大越难定位到底哪一行出了问题。第二捕获到异常后至少要记录日志哪怕是print。最忌讳的是except Exception: pass看起来程序没崩但错误被吞得无声无息上线后排查半天找不到原因非常气人。5. 综合实战与常见问题排查实录5.1 综合实战三次机会的登录校验器把for、while、异常处理串起来我写出一个很实用的小程序用户有3次机会登录每次都要输入用户名和密码密码错了要给明确提示输入类型不合法也要有兜底提示。这个小程序可以直接用来理解循环与异常的配合MAX_ATTEMPTS 3 attempt 0 correct_user admin correct_pwd python123 while attempt MAX_ATTEMPTS: try: username input(请输入用户名) password input(请输入密码) if not username or not password: raise ValueError(用户名和密码不能为空) if username correct_user and password correct_pwd: print(登录成功欢迎回来) break else: print(用户名或密码错误) except ValueError as e: print(输入有误, e) finally: attempt 1 else: print(尝试次数已用尽登录失败)这段代码有三处值得细看。第一外层用while控制尝试次数正好体验“次数未知但有上限”的循环场景。第二自定义校验逻辑用raise ValueError抛出然后被except接住程序不会因为异常中断。第三finally里统一累加attempt保证无论输入合法还是抛了异常每轮都会计数不会出现“输入一次异常导致获得无限次机会”的问题。实际运行效果请输入用户名 请输入密码123 输入有误 用户名和密码不能为空 请输入用户名admin 请输入密码wrong 用户名或密码错误 请输入用户名admin 请输入密码python123 登录成功欢迎回来看到没有第一轮按了回车程序没有崩而是提示输入不能为空这就是异常处理的价值。如果三次都失败最后一行else就会输出“尝试次数已用尽”。5.2 常见报错和排查技巧速查表我在带新人时发现循环和异常处理这块出的问题高度重复。我把高频问题整理成速查表现象/报错原因排查与解决程序跑个不停CPU占用高while条件永远为True或忘了更新计数器检查循环变量是否递增按CtrlC中断后再查IndexError: list index out of range下标越界确认列表长度循环里用len(list)做边界判断遍历字典时KeyError键不存在用get()方法或先in判断修改列表时结果怪怪的遍历过程中修改了正在遍历的列表先复制一份列表再遍历用list[:]异常了但控制台没有任何输出异常被吞掉了检查是否有except Exception: pass至少打印日志用except后程序还是崩异常类型没写对没接住对应异常确认异常类型临时改为Exception看日志循环里的变量丢了变量在循环外定义循环内重新赋值检查作用域用全局变量要声明global文件读出来乱码编码不一致open时指定encodingutf-8这里挑两个重点详细说。第一个是“遍历时修改列表”。你可能会写nums [1, 2, 3, 4, 5] for num in nums: if num % 2 0: nums.remove(num)看起来是想删掉偶数但Python在遍历时列表长度发生变化会导致跳过某些元素最终结果完全不符合预期。正确写法是先遍历副本nums [1, 2, 3, 4, 5] for num in nums[:]: if num % 2 0: nums.remove(num) print(nums) # [1, 3, 5]第二个是“try范围太大”。有些新手图省事把整个程序包进一个try结果第10行出错第20行也跟着不执行最后根本不知道问题出在哪。请记住异常处理是服务局部风险的不是整段代码的“免死金牌”。5.3 调试循环和异常的土办法但真的好用调试循环和异常最高效的方法未必是调试器。我平时最常用三个土办法。第一print定位法。在循环的关键位置打印变量值for i in range(5): result i * 2 print(i, i, result, result)不要小看print它简单直接能瞬间告诉你循环是不是进去了、变量在每一轮长什么样。调试完成后记得删掉否则控制台会被刷屏。第二缩范围法。异常报错时Traceback里会明确告诉你是哪个文件第几行出问题。我建议把出错的try块逐步缩小先注释掉后半段确认前半段是否正常再慢慢恢复很快就能定位到出错的那一行。第三复现输入法。异常和输入强相关时记下出错时输入的内容单独写个最小复现脚本跑一遍看看。有时候一个空字符串、一个超大数字、一个中文字符就会触发崩溃这种问题不亲自复现很难推断。5.4 给新手写循环和异常处理的几条总建议最后把这一篇涉及的实操经验浓缩成几条适合收藏起来经常回看写循环前先想清楚三件事初始化是什么、终止条件是什么、每轮怎么更新。能确定循环次数的用for次数不确定的用while不要混着用。循环体尽量短小超过20行请考虑拆函数。不要用while True写容易失控的逻辑必须有明确的break出口。try只包最容易出错的几行不要包整个函数。except要写具体异常类型能不用Exception就不用Exception禁止裸except。捕获异常后务必有日志输出禁止空pass。finally用于资源释放文件操作优先用with。主动使用raise让调用方知道你遇到了什么不合理的状态。调试时多用print临时看结果排查完记得清理。最后说一点个人体会。我在实际项目里写循环最大的感触是循环本身不难难的是想清楚“循环退出的条件”。很多线上问题不是代码逻辑写错了而是某个边界条件没控制住程序一直转圈或者异常被吞掉。所以写循环前我会先在纸上把变量走一遍写异常处理时坚持“try范围尽量小、异常类型尽量明确”这两条原则。这一篇把for、while和异常处理拆开讲完程序控制语句的大框架也就齐了。下一篇如果继续写可以聊聊函数那是把代码组织起来的下一层抽象。希望这篇对你有用。