ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python流程控制详解:条件判断、循环与异常处理实战

Python流程控制详解:条件判断、循环与异常处理实战 说实话Python这门语言能火到今天这个程度核心语法功不可没。我见过太多人一上来就pip install、requests、selenium爬虫跑得飞起但问到for循环和while循环到底什么区别、break和continue谁先执行一下子就愣住了。这就是典型的“地基没打牢就开始盖二楼”。这篇文章要聊的就是Python核心语法里的流程控制语句——条件判断、循环、循环控制还有经常被忽略的异常处理。说白了流程控制就是让程序学会“思考”的东西什么时候走这条路什么时候走那条路什么时候停下来重来一遍。不管你是刚看完安装教程的零基础新人还是已经能写点小脚本但总觉得代码不够利索的初学者把这一块吃透你后面学爬虫、数据分析、写自动化脚本都会顺很多因为所有项目本质上都是在“控制流程”。1. 流程控制到底在解决什么问题1.1 程序不是“一根筋”顺序、分支、循环可以回想一下你写完一个最简单的print(hello)程序从上到下逐行执行这叫顺序结构。但它只能处理“一条路走到黑”的情况。现实里的问题几乎都不是线性的。比如你写一个判断用户登录的程序用户输入密码密码对进入首页密码不对提示重新输入连续错三次锁定账号。这里每一步都是一个分岔路口程序必须根据当前条件决定下一步往哪走。这种“根据条件选路径”的能力就是流程控制的第一个核心——条件分支。再比如你要打印1到100的所有偶数总不能写100行print吧。你得让程序“重复做某一件事”这就是流程控制的第二个核心——循环。循环让代码有了复用能力一段逻辑可以跑成千上万次而且每次跑的数据还可以不一样。1.2 为什么说Python的流程控制更适合新手我最早学过一阵子C语言当时最痛苦的就是大括号。写一个if你得小心翼翼地把{}配对少一个括号编译直接报错。Python把这个事情彻底简化了它用缩进来表示代码块同一个缩进级别就是一个块。也就是说你的代码长什么样逻辑就是什么样。if score 60: print(及格了) print(这句也在if里面) print(这句在if外面)这种设计对新手极度友好你不用记那些“符号配对”只需要盯住缩进别乱。但它也带来一个著名的坑缩进错误。我后面会在常见问题那节单独说。总之Python把流程控制的“形式门槛”降到了最低让你把精力集中在逻辑本身而不是语法符号上。2. 条件分支让代码学会“看情况办事”2.1 if/elif/else的基本写法与缩进细节条件分支是Python里最常用的流程控制语句没有之一。基本结构是这样的temperature 36.5 if temperature 38: print(发烧了注意休息) elif temperature 37.3: print(低烧多喝水) elif temperature 36: print(体温正常) else: print(体温偏低注意保暖)注意几个细节if、elif、else后面都要有英文冒号:。漏了冒号会直接报SyntaxError。elif是else if的缩写Python里没有elseif这种写法多写一个空格都不行。分支只会执行第一个满足条件的那一个。比如温度是37.5它会走elif temperature 37.3这个分支然后整个if结构结束后面即使条件也成立也不会再执行。这里有个新手经常犯的迷糊为什么条件顺序会影响结果看上面例子如果你把temperature 36放在最前面那37.5也会命中它后面就永远不会走到了。所以写条件分支的时候范围大的条件往后放范围小的往前放这是一个很重要的经验。2.2 真值判断和逻辑运算的坑很多Python初学者写条件判断时喜欢写得很啰嗦if name ! and name is not None: print(名字非空)其实Python有一种“真值判断”的约定空字符串、空列表、空字典、0、None在条件表达式里都会被当成False。所以上面这段完全可以简化成if name: print(名字非空)反过来判断“非空”就是if not name。这种写法看着简单但逻辑完全一样而且运行效率略高。我自己写代码的时候基本不会去写! 这种判断除非有特殊需要。再说逻辑运算符。and、or在Python里有个特性叫“短路求值”and只要左边是假右边根本不会执行or只要左边是真右边也不会执行。比如user None name user.get(name) if user else 默认用户这里是借助三元表达式做了个空安全判断。但短路求值有个更加实际的应用场景避免程序崩溃。# 如果文件对象是None直接跳过读取 if file_obj and file_obj.readable(): data file_obj.read()如果file_obj是Noneand左边为假右边不会执行file_obj.readable()不会被调用也就不会报AttributeError。这个技巧在写爬虫、处理外部数据时特别常用。2.3 嵌套分支与Python 3.10的match语句有些时候分支里还要再分支这就出现了嵌套的if。但嵌套层数一多代码会变得很难读。我见过有人写出四层缩进的if那个代码基本没法维护。处理嵌套有两个思路第一个思路是“提前返回”。把每个不满足的条件写成if直接return后面再写主逻辑。这种风格叫guard clause中文可以叫“守卫子句”。def check_age(age): if age 0: return 年龄不合法 if age 18: return 未成年 if age 60: return 成年人 return 老年人第二个思路是用Python 3.10之后新增的match语句。它有点像其他语言里的switch但能力更强支持模式匹配match command: case start: print(启动服务) case stop: print(停止服务) case restart: print(重启服务) case _: print(未知命令)这里case _是默认分支类似于else。match很适合用来处理“根据某个值走不同分支”的场景比写一长串if/elif要清晰得多。不过需要注意如果你用的Python版本低于3.10match是用不了的这也是为什么我一直建议新手装最新稳定版Python的原因。2.4 实操经验条件判断的顺序和链式比较关于条件顺序除了“小范围优先”之外还有一个原则叫“高频优先”。比如用户输入的数据大概率是合法的那么就把合法判断放在前面异常情况放在后面这样大部分请求走第一个分支就结束了执行效率更高。虽然Python不慢但好习惯值得养成。Python还有一个非常好用的语法就是链式比较。其他语言里写if (x 5 x 10)Python直接写成if 5 x 10: print(x在5到10之间)这种写法既简洁又直观我第一次看到的时候觉得“这也太爽了吧”后来写C#的时候老想着这么写可惜人家不支持。3. 循环结构while和for怎么选3.1 while适合“不知道要循环几次”的场景while循环的语法很简单条件为真就继续执行循环体为假就退出。我习惯把它理解成“只要...就一直...”。count 0 while count 5: print(f第{count 1}次) count 1这里有个很重要的点循环体里一定要有改变条件的语句比如count 1。如果没有条件永远为真程序就会陷入无限循环。我当年刚学的时候有一次忘记写count 1控制台直接把数字刷了满屏最后只能强制关闭终端。while真正的用武之地是那些“不知道要循环多少次”的场景。比如等待用户输入一个合法值answer input(请输入一个偶数: ) while not answer.isdigit() or int(answer) % 2 ! 0: answer input(输入不合法请重新输入一个偶数: ) print(f你输入的是{answer})这种循环次数完全由运行时的输入决定的场景用while最合适。所以记住一个大概的判断标准知道循环次数用for不知道次数用while。3.2 for range最经典的遍历组合for循环在Python里其实是一种“遍历”机制。它不像是C语言那样“i0; i10; i”的计数器循环而是“从某个序列里一个一个取元素”的遍历循环。for i in range(5): print(i) # 输出0到4range有三个参数可以调range(起始, 结束, 步长)。起始默认是0步长默认是1结束值不包含在内。比如for i in range(1, 10, 2): print(i) # 输出1,3,5,7,9 for i in range(10, 0, -1): print(i) # 输出10到1for循环最常用的场景是遍历列表、元组、字典这些容器。比如遍历列表fruits [apple, banana, cherry] for fruit in fruits: print(fruit)这种写法非常直观每个元素都会被拿出来处理一遍。需要注意的是遍历过程中尽量不要修改容器本身这个坑我后面单独讲。3.3 遍历字典、字符串的小技巧字典本身就是可迭代对象直接for key in dict遍历的是它的键。如果你想要键值对就要用.items()方法score_dict {小红: 90, 小明: 85, 小刚: 78} for name, score in score_dict.items(): print(f{name}考了{score}分)这里name, score就是解包。如果只需要值可以用.values()只需要键直接遍历字典本身或.keys()都可以。再一个是同时拿索引和值时用enumerate。我见过不少新手写这样的代码index 0 for fruit in fruits: print(index, fruit) index 1这个逻辑没问题但完全可以用enumerate一行搞定for index, fruit in enumerate(fruits, start1): print(index, fruit)enumerate的第二个参数start可以指定起始编号默认是0。这样代码更简洁也不容易漏掉index 1这一步。如果你有两个列表想“按位”配对遍历可以用zipnames [张三, 李四, 王五] scores [88, 92, 76] for name, score in zip(names, scores): print(name, score)zip会把多个可迭代对象按索引组合成元组然后一次性解包。这个技巧在做数据分析和处理表格时非常香。3.4 嵌套循环打印乘法表与图形入门嵌套循环就是循环里套循环。最经典的练习就是打印九九乘法表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)控制了每行输出的数量让它随i增大而增加end\t让print不换行而是输出制表符外层循环里那个单独的print()负责换行。类似的打印一个直角三角形for row in range(1, 6): for col in range(1, row 1): print(*, end ) print()嵌套循环是学习流程控制时的一个坎但只要理解“外层循环控制行内层循环控制列”这个口诀基本就能搞定。你还可以试着用两层循环画一个爱心这个和网上很火的“python爱心代码”思路一样本质也就是控制字符输出的位置。3.5 边遍历边修改列表几乎是必踩的坑我在带新人时发现几乎每个人都会在某天踩到这个坑——遍历一个列表同时想把满足条件的元素删掉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,6]为什么6还在因为for循环内部是通过索引遍历的。删除元素后列表长度变了索引对应关系全乱了那个6根本没轮到就被跳过了。这种问题很隐蔽有时候数据量大一点你根本发现不了。解决办法有三种遍历副本for num in numbers[:]:对副本遍历对原列表修改。用列表推导式重新生成numbers [num for num in numbers if num % 2 ! 0]。倒序遍历for num in reversed(numbers):从后往前删不会影响前面元素的索引。我个人的习惯是能不用remove就不用列表推导式一行搞定而且语义更清楚。这个经验在你以后处理爬虫数据清洗、日志筛选时会经常用上。4. 循环的控制权break、continue、else、pass4.1 break立刻离开整个循环break是最简单直接的控制语句执行到它立刻跳出当前这层循环。比如你要在列表里找第一个大于10的数numbers [3, 8, 15, 22, 9] for num in numbers: if num 10: print(f找到了: {num}) break如果没有break这个循环会继续把后面的数也遍历完。有了break找到目标就停下效率高逻辑上也更符合“找到一个就够了”的语义。注意break只会跳出它所在的那一层循环如果你在嵌套循环的内层使用外层循环不受影响。4.2 continue跳过当前这一次继续下一次continue的作用是“跳过本次循环的剩余代码直接进入下一次循环”。比如只打印奇数for i in range(10): if i % 2 0: continue print(i)执行到continue时后面的print(i)不会执行直接进入下个i。这个和break的区别要记牢break是结束整个循环continue是结束当前这一次迭代。我经常用continue做数据过滤比如爬虫里跳过不符合规则的响应继续处理下一条。4.3 循环后的else判断循环是否“正常走完”这是Python一个比较冷门但很好用的特性for循环和while循环后面可以跟一个else当循环正常结束没有被break中断时会执行else里的代码。经典的例子是判断一个数是不是素数num 17 for i in range(2, num): if num % i 0: print(f{num}不是素数) break else: print(f{num}是素数)如果num能被某个i整除就执行break跳出else不会执行如果循环从头走到尾都没有break说明没有任何约数此时执行else判定为素数。这个写法比用一个flag变量标记“是否是素数”要简洁得多。我第一次看到这个特性的时候觉得很神奇但后来确实越用越顺。唯一需要注意的是很多人不了解这个语法反而觉得难以理解所以如果你是在团队项目里使用最好在注释里说明一下意图。4.4 pass啥也不干的占位符pass是一个空语句不做任何事情。它的作用就是占位。比如你还没想好某个分支具体怎么实现可以先写上pass保证代码不报错if condition: pass # 之后再来实现 else: print(其他情况)很多初学者不理解“为什么需要pass这么个没用的东西”。因为Python用缩进表示代码块一个代码块里如果一行内容都没有语法就会报错。pass就是用来“占据”这个位置让程序能正常运行起来。记住一个原则pass是给开发阶段用的不是给生产逻辑用的。真正的业务逻辑里出现pass大概率是代码还没写完。4.5 综合小例子登录重试机制把这些控制语句串起来写一个非常常见的场景登录重试最多试3次。MAX_RETRY 3 password 123456 for attempt in range(1, MAX_RETRY 1): user_input input(请输入密码: ) if user_input password: print(登录成功) break else: remaining MAX_RETRY - attempt if remaining 0: print(密码错误次数过多账号已锁定) else: print(f密码错误还剩{remaining}次机会) else: print(三次机会已用完)这一段代码里用了for、if/else、break、循环后的else几乎覆盖了前面所有知识点。break在登录成功后退出循环然后else不会执行如果循环正常结束说明三次都失败了执行else打印锁定提示。这个结构在写自动化脚本、爬虫重试逻辑时非常常见。5. 异常处理被忽略的“第四种流程控制”5.1 什么是异常为什么必须在流程控制里讲异常处理往往被单独拎出来讲但我觉得它就是流程控制的一部分。程序运行的时候输入不可控、网络不可控、文件可能不存在各种意外随时会发生。如果你不处理这些意外程序会直接崩溃给用户留下一屏看不懂的报错。异常处理的本质是“当程序发生错误时走另一条流程”。这和if本质上没有区别只是它判断的条件是“有没有发生异常”。所以我把异常处理当成流程控制的第四个家族成员而且是写健壮代码必须掌握的。5.2 try/except/else/finally的基本结构Python里异常处理的关键字有四个try、except、else、finally。基本结构是try: # 可能出错的代码 num int(input(请输入一个整数: )) except ValueError: # 出错时执行 print(输入的不是整数) else: # 没出错时执行 print(f你输入的是{num}) finally: # 不管出错与否都会执行 print(本次输入流程结束)四个部分的执行顺序是先执行try里的代码如果抛异常跳到对应的except执行如果没抛异常执行else最后无论如何都执行finally。finally的用途很明确释放资源。比如关闭文件、关闭数据库连接、关闭网络请求。有些初学者会忘记用finally直接把关闭操作写在try的最后面但这样一旦中间出错后面的关闭代码就不会执行了资源一直被占着程序多了就会出问题。5.3 捕获多个异常和异常对象一个try块可以跟多个except用来处理不同类型的错误try: data {name: 小明, age: 18} age data[age] result 100 / int(age) print(result) except KeyError as e: print(f键不存在: {e}) except ZeroDivisionError as e: print(f除零错误: {e}) except ValueError as e: print(f类型转换错误: {e})这里as e是把异常对象赋值给变量e方便我们打印出具体的错误信息。注意except的顺序也很重要如果异常类型之间有继承关系子类的except要写在父类前面否则子类的处理永远不会被命中。还有一种写法是把多个异常类型放在一个元组里except (KeyError, ValueError) as e: print(f数据问题: {e})这种写法适合“这两种异常走同样的处理逻辑”的场景。5.4 raise主动抛出异常的时机raise是主动抛出异常的关键字。什么时候需要主动抛答案是当程序遇到一个“自己处理不了”的情况时应该让上层代码知道。比如你写了一个函数负责从用户输入里提取年龄结果发现输入是负数def get_age(): age int(input(请输入年龄: )) if age 0: raise ValueError(年龄不能为负数) return age调用方可以捕获这个异常也可以继续向上抛。这个机制的好处是你不需要通过返回值来“告知”调用方出错异常本身就是最好的错误信息载体。新手容易犯的错误是到处try/except把所有错误都吞掉这样反而掩盖了问题。正确的姿势是能处理的就地处理处理不了的raise让上层处理实在处理不了就让程序崩掉至少你能看到报错。5.5 实例让用户输入数字直到正确把循环和异常处理结合写一个更实用的输入校验工具while True: raw input(请输入一个数字: ) try: num float(raw) except ValueError: print(输入无效请重新输入) continue if num 0: print(数字不能为负数请重新输入) continue print(f合法输入: {num}) break这个例子里while True配合break是一个经典的输入循环结构。try/except负责捕获“字符串转数字”失败的情况continue让用户重新输入条件判断负责业务规则break在拿到合法输入后退出循环。这段代码几分钟就能讲完但它串起了条件和循环几乎所有知识点而且你以后写命令行工具时可以直接抄这套模板。6. 综合实战用流程控制写一个“猜数字”小游戏6.1 需求拆解讲完一堆语法点我们来做个小项目把它们串起来。经典的控制台小游戏“猜数字”特别适合程序随机生成一个1到100之间的整数。用户输入猜测的数。程序提示猜大了、猜小了还是猜对了。用户猜错就继续猜猜对就结束并显示次数。如果用户输入的不是数字要提示重新输入不能崩。这个需求天然包含随机数、while循环、if/elif/else判断、break退出、异常处理几乎把我们今天讲的内容全部用上了。6.2 完整代码import random target random.randint(1, 100) attempts 0 print(欢迎来猜数字范围是1到100) while True: raw input(请输入你猜的数: ) try: guess int(raw) except ValueError: print(请输入一个有效的整数) continue attempts 1 if guess target: print(猜大了再试试) elif guess target: print(猜小了再试试) else: print(f恭喜你猜对了一共猜了{attempts}次) break6.3 代码解读每个知识点出现在哪里整个程序的骨架是while True无限循环直到break才退出这就是“不知道要循环几次”的经典场景。try/except处理了输入不是整数的情况continue让流程回到循环开头重新输入不会因为输入非法而崩溃。if/elif/else三兄弟承担了核心判断逻辑根据用户输入和目标数字的大小关系给出不同反馈。attempts 1就是循环里最常见的“计数器”写法最后在else分支里把计数结果展示出来。这个代码可以继续扩展比如加入“最多猜10次”的限制超过次数就break并输出目标数字也可以把最高分记录写进文件里下次玩的时候读取。爬虫、数据分析这些更复杂的项目底层逻辑也是一样的判断状态、获取数据、处理异常、循环重试。6.4 扩展方向如果你把这个小游戏改造成“猜数字并记录历史成绩”就会用到文件读写改造成“多人轮流猜”就会用到列表和函数改造成“网络版”就会用到socket。每一个扩展方向都会引入新的知识点但核心的流程控制部分已经在这里了。我就是靠这种“小游戏小工具”的方式一路把Python基础打牢的比干啃语法书效果好太多。7. 常见问题与调试技巧实录7.1 新手最容易踩的几个坑我把这些年亲眼见过的报错和逻辑错误整理了一下基本逃不出下面这几个问题表现原因解决办法IndentationError缩进错误代码块层级混乱混用空格和Tab或缩进不一致统一用4个空格编辑器开启“显示空白字符”SyntaxError: expected :if/while/for后面漏冒号英文冒号忘了写检查每行条件语句结尾UnboundLocalError局部变量引用报错函数内部和外部的变量名冲突用global声明或改名无限循环程序卡死刷屏while条件永远为真检查循环里是否有更新条件的语句break只跳出一层嵌套循环里退出不彻底break只作用于最近一层使用“标志位”或把逻辑封装成函数用returnIndexError列表索引越界访问了下标不存在的元素打印列表长度检查range边界还有一个很隐蔽的逻辑错误就是赋值和比较混淆。写if guess target这是语法错误因为是赋值才是比较。但在某些语言里这种错误不会报语法错所以很多从其他语言转过来的朋友会在这里栽跟头。Python还算好的至少会直接报错不会让你默默地去比较。7.2 调试技巧print大法依然是神很多初学者一遇到程序不按预期跑就慌了到处乱改。我的建议是先用print把关键变量打印出来看看程序到底走到了哪个分支。还是拿猜数字举例如果你发现不管猜什么都说猜大了可以在判断前打印一下目标数字print(f调试信息: target{target}, guess{guess})这样你一眼就能看出来随机数的范围是不是不对或者是不是比较符号写反了。这种“print大法”虽然简单但真的能解决80%的流程问题。等你熟练了可以再学pdb断点调试但刚开始真没必要用那么重的工具。7.3 一段真实排查实录有一次我帮朋友看代码他的需求很简单遍历一个用户列表把所有状态为“停用”的用户找出来打印他们的名字。代码长这样users [ {name: 张三, status: 正常}, {name: 李四, status: 停用}, {name: 王五, status: 正常}, ] for user in users: if user[status] 停用: print(user[name])看起来一点问题没有但他跑出来结果为空。我让他打印一下user[status]结果发现数据里的状态值其实是停用 后面多了一个空格。类似这种“看不见的字符”问题在爬虫和数据处理中太常见了。排查办法很简单输出前后加个标记print(f[{user[status]}])这样空格就显形了。我特别想告诉刚入门的朋友当程序没按预期走流程别急着怀疑语法先看看变量值到底是什么。数据不对流程再对也没用。7.4 关于调试流程的几个个人建议最后分享一点调试流程控制的小心得。第一把大问题拆小。比如一个复杂的多条件判断先注释掉一半条件看看另一半是否正常逐步缩小排查范围。第二不要凭感觉改代码。每改一个地方就跑一次确认影响了什么这样能避免“修好这个又弄坏那个”。第三善用else分支打印“不应该走到这里”的提示。比如在if链的最后加一个else: print(意外情况:, 变量)帮你尽早发现逻辑漏洞。这些习惯看起来不起眼但在写几百行项目的时候能帮你省下大量时间。我个人在实际带人过程中体会最深的一点是很多人学流程控制时觉得“太简单了”一看就懂但一写就错。真正的问题往往不是语法不会而是没有把“程序是一步一步执行”这件事记在脑子里。每当你写下一个if或者for都可以试着把自己当成Python解释器沿着代码走一遍看看每一步会执行到哪一行、变量变成什么值。这样走几遍你对流程控制的理解就会完全不一样。
返回列表