系列教程更新到第四篇前几篇里我们把Python的环境搭建、变量与数据类型、输入输出和运算符都过了一遍。有读者留言说跟着教程装好Python、能跑通print之后下一步就不知道该学什么了——这很正常因为到目前为止你写的代码都是从上到下一行一行往下走的程序不会“思考”。从这篇开始程序终于拥有了最简单的智能根据不同的情况走不同的路。这就是流程控制语句具体来说是if...elif...else和match...case。这篇文章会从最基础的概念讲起一步步拆解分支语句的语法、原理和实战用法哪怕你是零基础、刚折腾完python安装的新手也能跟着写完并跑通。后面还会讲到我用Python这些年踩过的一些坑以及条件判断到底怎么写才又清晰又不容易出bug。1. 流程控制语句为什么是Python程序的“岔路口”1.1 没有流程控制的代码长什么样先回忆一下前几篇的代码。不管是变量赋值、字符串拼接还是print输出代码都是按顺序往下执行的就像一条单行道name 张三 age 18 print(name) print(age)这种结构叫“顺序结构”。它本身没什么问题但如果程序只能这么写那它什么事也干不了。你想写一个“根据分数判断是否及格”的功能都不行因为程序没有“判断”的能力。判断就是流程控制里最核心的分支逻辑。我在带新手的时候经常会打一个比方程序就像你开车出门顺序结构是一条没有分岔的高速路你只能一直往前开。而真实世界的导航不是这样的——前方堵不堵、目的地停车位够不够、要不要绕路都得在岔路口做选择。流程控制语句就是程序里的那个岔路口。1.2 分支、循环与顺序三大基本结构的定位计算机科学的祖师爷早就总结过任何复杂的程序逻辑都可以由三种基本结构组合出来顺序、分支、循环。顺序从上往下逐行执行这是所有程序的地基。分支根据条件成立与否决定走哪一段代码也就是if语句、match语句干的事。循环满足条件时重复执行一段代码对应for和while。今天这篇主讲分支。循环语句我会放到后面专门写一篇但分支学好了循环里的很多判断逻辑你才能顺手接住。有人可能会问既然有if为什么Python 3.10还要出match...case这不重复吗这个问题的答案要放到后面讲match的时候详细展开。简单说if擅长处理“区间判断”和“复合条件”而match擅长处理“离散值分发”和“结构匹配”。两者互补不是替代关系。2. if...elif...else基础语法从单分支到多分支一次讲清2.1 最简单的if单分支结构if单分支是最基础、最常用的写法。它做的事情很简单条件成立执行里面的代码不成立跳过。age 20 if age 18: print(你已经成年了)运行结果会打印“你已经成年了”。如果把age改成16这个程序什么都不会输出。这里有两个关键的语法细节新手第一次写特别容易错第一条件后面必须加英文冒号:。很多从其他语言转过来的朋友会不习惯总觉得少了什么。这个冒号是Python语法的固定组成部分表示“下面是一个代码块”。第二if下面的代码必须缩进。Python用缩进来划分代码块的层级而不是像C语言、Java那样用花括号。一般约定缩进4个空格。如果你不缩进运行时会直接报IndentationError。我建议在编辑器里把Tab键自动替换成空格。Pycharm、VS Code都有这个设置把“Tab and Indents”里的“Use tab character”取消勾选即可。原因很简单空格和Tab混用会引发非常诡异的缩进错误而且肉眼几乎看不出来。2.2 双分支与多分支if...else和if...elif...else单分支只能处理“成立做什么不成立什么都不做”的场景。但现实里更常见的是非黑即白及格还是不及格成年还是未成年。这时候需要else。age 16 if age 18: print(你已经成年了) else: print(你还未成年)else不需要写条件它表示“上面的条件都不成立时执行这里的代码”。那三个及以上分支呢比如成绩等级优秀、良好、中等、及格、不及格。这时候就用elif。“elif”是“else if”的缩写写法上合并成一个词简化了嵌套层级也更易读。score 85 if score 90: print(优秀) elif score 80: print(良好) elif score 70: print(中等) elif score 60: print(及格) else: print(不及格)这段代码的运行逻辑是这样的从上往下依次判断哪个条件先成立就执行哪个分支后面所有分支不再执行。除此之外elif可以写很多个但else最多一个而且必须放在最后。有个非常重要的点我每次讲课都会强调分支条件的顺序是有讲究的。上面这段代码能正常工作是因为我们按从高到低的顺序判断。如果你反过来写if score 60: print(及格) elif score 80: print(良好)那85分只会打印“及格”因为第一个条件已经成立了后面的elif根本没机会执行。这种错误叫“短路过早”运行不会报错但结果就是错的是最隐蔽的逻辑bug之一。后面第6章我还会专门列一个排查清单。2.3 条件表达式到底在判断什么if后面那些条件本质上都在计算一个布尔值True或者False。比较运算是最常见的条件来源。Python支持的比较运算符包括运算符含义示例等于age 18!不等于age ! 18小于score 60大于score 90小于等于age 17大于等于age 18注意判断相等是两个等于号一个等于号是赋值。这个坑几乎所有Python新手都踩过我后面单独讲。多个条件组合时用逻辑运算符and且、or或、not非。优先级从高到低是not and or。age 20 has_ticket True if age 18 and has_ticket: print(可以进场)这一段表示两个条件同时成立才执行。“and用于同时满足or用于满足其一”这个理解很直观。但有件事新手容易忽略Python的条件判断不完全是True/False两端点它还遵循“真值测试”规则——任何对象都可以直接用作条件。在if判断中以下几种情况会被当成False布尔值False数字0和0.0空字符串空列表[]、空元组()、空字典{}和空集合set()None其他所有值都被当成True。所以你可以直接写name input(请输入名字) if name: print(你好, name) else: print(名字不能为空)这比写“if name ! ”更简洁、更Pythonic。很多老手写代码时都喜欢利用这个真假值特性。3. if条件判断的进阶经验别把代码写成“俄罗斯套娃”3.1 条件太复杂时先拆变量再判断新手的代码里经常能看到这样的条件if user is not None and user.status active and user.age 18 and user.city 北京: print(允许进入)一行代码四五个条件看得人头皮发麻。如果某个条件不满足排查的时候得一个个拆开试效率极低。我会建议把复杂的条件拆出来赋给有意义的变量名is_active_user user is not None and user.status active is_adult user.age 18 is_in_beijing user.city 北京 if is_active_user and is_adult and is_in_beijing: print(允许进入)这段代码逻辑完全一样但可读性天差地别。每个条件是什么一目了然后续如果要改条件直接改变量定义那一处就行。这是实战中非常实用的优化技巧看起来没加多少代码但维护体验完全不一样。3.2 三元表达式简化简单的if...else赋值Python里有一种简洁的写法叫三元表达式也叫条件表达式语法是“值1 if 条件 else 值2”。条件成立取值1否则取值2。age 20 status 成年 if age 18 else 未成年 print(status)这个写法的适用场景非常明确if和else里都只是简单的赋值或返回没有复杂逻辑。一句话就能写完代码干净。但三元表达式有个大忌不能滥用。如果你在表达式里嵌套另一个三元表达式或者把很长的函数调用写进去可读性会迅速恶化。比如result 优秀 if score 90 else 良好 if score 80 else 中等这种链式三元虽然能跑但阅读时需要花很多精力去解析优先级远不如用if...elif...else清晰。我的原则是只有单个if...else的场景才用三元表达式多分支场景一律用传统写法。3.3 嵌套if一定不好吗不一定很多人听过“嵌套if影响可读性”的说法于是遇到任何情况都强行用and合并。其实这不一定是对的。比如逻辑本身就有明确的先后顺序if user: if user.is_admin: print(管理员界面) else: print(普通用户界面) else: print(请先登录)这种嵌套是有意义的先判断“用户是否存在”再判断“是否管理员”两个问题的层次不同。如果硬要合并成一个条件“if user and user.is_admin”就丢失了“用户不存在”这一层信息else分支就没法精确处理未登录的情况了。再举一个提前返回的例子这个在函数里特别常用def process_order(order): if order is None: return 订单为空 if not order.is_paid: return 订单未支付 # 真正的业务处理 print(处理订单) return 处理完成这种写法把异常情况前置每个if只负责一个边界判断主流程顺畅地留在最后。阅读体验非常好也不存在多层嵌套的问题。所以我的建议是嵌套本身不是问题问题是“无意义的深层次嵌套”。如果嵌套层级超过三层通常说明逻辑设计可以优化要么调整结构要么抽成函数。如果继续深挖下去代码的复杂度会指数级增长那不是缩进风格能救回来的。4. match...case结构化模式匹配Python 3.10带来的新解法4.1 match...case到底解决了什么问题Python 3.10引入了一个新特性叫结构化模式匹配语法关键字是match...case。很多人一看到它就说“Python终于有switch了”这种理解只对了一半。它跟其他语言里的switch确实有点像——都是拿一个值去跟多个分支比较——但match的能力比switch强得多。switch只能做等值比较match能做结构匹配。这句话怎么理解等值比较是“这个值等于多少”结构匹配是“这个数据长什么样”。它可以匹配元组、列表、字典甚至对象的属性还能把匹配到的部分直接提取出来赋值给变量。这就很厉害了。它主要解决的是if...elif...else链在一些特定场景下的冗长问题。比如要根据用户的命令字符串分发到不同处理逻辑用if写会有一长串“xxx start”这种重复代码用match写则干净很多。4.2 基本语法与最简单的用法match...case的基本结构是这样的command start match command: case start: print(启动服务) case stop: print(停止服务) case restart: print(重启服务) case _: print(未知命令)运行时会拿command的值去依次跟每个case后面的“模式”做匹配命中了就执行对应代码然后整个match语句结束。这里的下划线“_”是通配符等价于if里的else表示“什么都不匹配时走这里”。有几个细节必须注意第一case后面不需要写break。其他语言里的switch如果漏写break会发生“穿透”但Python的match设计成匹配成功后自动终止从根源上避免了这个问题。第二_符号不是变量它只是一个通配标记表示“我不关心这里是什么值”。第三一定要给match写兜底分支。如果所有case都没匹配到而且没有case _程序不会报错但它会静默地什么都不做。这种静默失败非常坑调试的时候很难发现。4.3 进阶玩法从等值比较到结构匹配match真正拉开差距的地方在于它能匹配“结构”。先说最简单的组合模式一个case里可以用“|”表示多个值都匹配。code 404 match code: case 404 | 500: print(服务器错误) case 200: print(请求成功) case _: print(其他状态码)然后是序列模式匹配列表或元组的结构并提取元素。point (3, 5) match point: case (0, 0): print(这个点位于原点) case (x, y): print(f点的坐标是({x}, {y}))这里不需要提前写x point[0]这种赋值case直接帮你解包了。对于处理坐标、二元组这类结构这个能力非常方便。甚至可以匹配字典结构这在处理JSON数据时很实用data {name: 张三, age: 18} match data: case {name: name, age: age}: print(f名字是{name}年龄是{age}) case _: print(数据格式不正确)匹配成功后name和age这两个变量会自动绑定到字典里对应的值。还有守卫条件就是在case模式后面加一个if给匹配再加一道关卡score 85 match score: case _ if score 90: print(优秀) case _ if score 80: print(良好) case _ if score 60: print(及格) case _: print(不及格)这里“case _”表示“任何值都匹配”后面的if再进一步条件判断。这就是match处理区间判断的通用写法。不过老实说类似这种区间分级的需求用if...elif...else会更自然一点这一点我后面实战部分会细说。4.4 match和if应该怎么选写代码碰到分支选if还是match我根据自己的实际经验总结了这么几个判断标准第一如果是离散的等值判断且分支数量比较多用match更清晰。典型场景是状态机、命令分发、菜单选项。第二如果是区间判断、范围判断比如分数等级、年龄分段用if更顺手。match写守卫条件虽然能实现但可读性不如if直观。第三如果需要同时判断多个变量之间的关系或者在判断时还想提取数据结构里的内容match的优势就很明显。if写这个会非常啰嗦。第四版本兼容性要心里有数。match语法是Python 3.10才有的如果你的运行环境是3.9或更早强行写会直接报语法错误。至于怎么确认版本运行python --version就行。对比项if...elif...elsematch...case适用场景区间判断、复合条件、真假值判断离散值分发、结构匹配可读性条件复杂时更好读分支多且是等值比较时更清晰输入结构提取不支持得手动解析支持序列、字典的解包提取版本要求所有Python版本Python 3.10及以上语法风格贴近自然语言灵活更像函数式语言里的模式匹配所以我一般跟学员说先把if用熟练match是锦上添花。但既然语言提供了这么好用的语法糖该用的时候就应该用起来前提是你清楚它的边界在哪里。5. 实战案例成绩等级与命令分发器的两种写法对比5.1 场景一成绩等级转换需求很简单输入一个0到100的分数输出对应等级。90分及以上为“优秀”80到89为“良好”70到79为“中等”60到69为“及格”60以下为“不及格”。用if来实现score int(input(请输入分数)) if score 90: print(优秀) elif score 80: print(良好) elif score 70: print(中等) elif score 60: print(及格) else: print(不及格)这段代码逻辑非常清晰从上往下依次判断每个区间的边界条件一目了然。这里有个容易忽略的细节int(input(...))把用户输入转换成整数了。如果你直接拿字符串跟数字比较会报类型错误。用match来实现score int(input(请输入分数)) match score: case _ if score 90: print(优秀) case _ if score 80: print(良好) case _ if score 70: print(中等) case _ if score 60: print(及格) case _: print(不及格)两者都能运行结果完全一致。但你看这个例子match版本并没有比if版本更简洁甚至还要多理解一层守卫条件的写法。这种情况下我明确推荐用if——区间判断本来就是它的主场。5.2 场景二命令行工具的分发器再看另一个场景做一个简单的命令行工具用户输入start、stop、restart、status时程序执行对应的功能。用if来写command input(请输入命令).strip().lower() if command start: print(服务启动中...) elif command stop: print(服务已停止) elif command restart: print(正在重启服务) elif command status: print(服务运行中) else: print(不支持的命令)这段代码不丑但你会发现每个elif都要重复写一遍“command ”分支越多重复越明显。如果以后要加十几个命令这个函数会变得又长又啰嗦。用match来写command input(请输入命令).strip().lower() match command: case start: print(服务启动中...) case stop: print(服务已停止) case restart: print(正在重启服务) case status: print(服务运行中) case _: print(不支持的命令)对比非常直观。match直接把command这个变量“摆”在最前面每个case只需要写一个字符串常量。少了重复的等值判断代码密度更高也更干净。这个例子就能看出两者的定位差异当判断逻辑只是“变量等于某个固定值”时match就是更合适的选择。我在实际工作中处理类似“命令路由”的模块时现在都会优先考虑match。5.3 场景三用match处理元组结构最后给一个if很难写、但match非常轻松的场景根据二维坐标判断位置。point (5, 8) match point: case (0, 0): print(原点) case (0, y): print(f在Y轴上y {y}) case (x, 0): print(f在X轴上x {x}) case (x, y): print(f普通点({x}, {y}))同样的逻辑用if写你得先拆开point[0]、point[1]再依次判断是否为0、是否为原点代码至少多写一倍而且可读性差很多。match直接把元组结构写进case里同时还把感兴趣的坐标值绑定成变量一步到位。这种结构匹配能力才是match真正区别于传统switch的地方。它让代码描述的是“数据应该长什么样”而不是“如果第0个元素等于...”这种机械流程。6. 新手常踩的坑语法错误、条件顺序与match陷阱排查笔记6.1 最容易报错的五个语法问题我在带新手的这几年里发现大部分早期报错都集中在几个固定的地方。整理成一个速查表方便你在出错时快速定位。报错信息常见原因解决办法SyntaxError: invalid syntaxif条件后漏冒号或误用单个号检查行尾冒号检查条件里是否用了IndentationError: expected an indented blockif下方没有缩进代码在if语句下方统一缩进4个空格IndentationError: unindent does not match any outer indentation level空格和Tab混用关闭编辑器的Tab替换为空格或者全部改成Tab保持统一SyntaxError: expected :elif或else前一行缺少冒号逐行检查if/elif/else行尾是否有冒号SyntaxError: positional argument follows keyword argument括号忘写或参数顺序写错仔细核对print等函数调用的括号新手最常见的其实就三种缺冒号、忘缩进、写成。每次运行报语法错误优先检查这三处命中率极高。6.2 条件顺序错误程序不报错但结果是错的这是我在实战里踩过的最坑的一类问题。程序能正常运行但输出跟你预期不同。典型例子score 85 if score 60: print(及格) elif score 80: print(良好) elif score 90: print(优秀)我故意把条件顺序调乱了结果就是85分只输出“及格”。原因前面讲过if条件成立后后面所有的elif都不会再判断。这种错误用语法检查工具根本发现不了唯一的防线就是你自己在写分支的时候保持清醒——条件范围窄的、严格的要先写范围宽的放后面。另一个经典错误是边界值。比如判断“小于60不及格60及以上及格”如果你把60分划到了不及格那边那就是边界值没处理好。测试的时候一定要专门测边界值60、59、90、89、0、100每一条边界都必须跑一遍。6.3 match的隐藏陷阱版本、捕获变量与兜底分支match虽然好用但有几个陷阱跟if完全不同刚开始用很容易翻车。第一坑版本。在Python 3.9及以下版本写match语句会直接报SyntaxError。这是语法层面的不支持不是你的代码写错了。安装新一点的Python版本就能解决。第二坑case后面的变量是“捕获”而不是“比较”。我见过有人这样写target start command stop match command: case target: print(命中target)他本意是拿command和target的值比较但实际上这个case几乎会匹配任何值因为它把command的字符串“捕获”并绑定给了target这个变量。如果需要比较变量值不能用这种写法得用守卫条件match command: case _ if command target: print(命中target)这个坑特别隐蔽因为它不报错只是逻辑不对。我建议match里的case后面尽量只写字面量或结构模式不要指望它帮你做变量等值比较。第三坑漏写兜底分支。match的静默失败会让程序在“什么都没匹配”的情况下安静地继续往下走你不容易察觉。我的习惯是每个match都写case _哪怕只是打印一条调试日志也能保证程序对未知情况有明确的响应。6.4 几个排查思路的实操建议如果你写的分支代码出了bug光盯着屏幕看往往看不出问题。我更推荐用下面这几个方法调试一是插入临时print标记。在每个分支里加一行print(进入分支A)运行后看打印了哪行就能确认条件判断的实际走向。排查完后删掉这些临时代码即可。二是用二分法注释代码。如果一段代码既长又复杂先把一半注释掉看问题是否还存在通过这种方式快速缩小出问题的范围。这个方法不仅适用于分支对所有代码调试都管用。三是把条件抽取成变量一行一行检查。复杂条件拆成变量后你可以在每个变量下面加print看看它到底算出来是True还是False判断逻辑哪个环节出了问题一目了然。我个人在实际操作中的一个体会是流程控制语句本身非常简单难点永远在“条件怎么组织”和“顺序怎么安排”上。把分支的条件当成你平时写需求时的思考过程而不是当成语法任务来背很多逻辑问题就会自然地解决。如果你在这篇文章里只能记住一句话那我希望是这句写完分支逻辑后一定要测边界值、测异常输入、测每个分支至少一次——这个过程能帮你避开绝大多数隐藏的坑。