跳转到正文

第四章:if 语句与分支结构

更新时间: 2026-10-11 01:07 · 中文版
目录 (19)

章节导航:上一章:第三章 常用内置函数与函数调用

在前三章中,程序均按照从上到下的单一路径顺序执行:解释器依次读取并运行每一行代码。这种流程固定的 顺序结构(Sequential Structure) 适合处理步骤确定的计算任务。

然而,现实业务往往充满条件与选择:网购订单满 99 元免除运费,否则收取基础运费;账户余额充足时扣款成功,否则提示支付失败;考核成绩达到 60 分为及格,否则为不及格。这些业务规则的核心逻辑是:根据运行时的状态进行判断,进而决定执行哪条路径上的程序。

条件分支结构:现实业务中的条件与选择示意图

在第一章 1.5 节中,我们学习了比较运算与逻辑运算(运算结果为 True 或 False)。 〔提示 2〕本章将引入条件分支结构(Conditional Branching),通过 if 语句赋予程序根据条件动态决策的能力。

本章分为两大部分:第一部分:核心内容讲解单分支(if)、双分支(if/else)与多分支(if/elif/else)的语法规则与执行机制,逻辑运算符(and、or、not)的组合应用与短路求值特性,以及分支嵌套与在自定义函数中的整合应用;第二部分:扩展知识点介绍简洁的双选条件表达式(三元运算),以及利用 return 提前退出函数以化简嵌套逻辑的卫语句模式。


第一部分:核心内容

4.1 条件成立时才执行:if 语句

4.1.1 按条件决定是否执行代码块

以电商系统的包邮规则为例:订单金额达到 99 元时免除运费,未达到时收取 6 元基础运费。

利用比较运算符,我们可以判断订单金额是否达到免运费门槛,并将运算结果保存在布尔变量中:

python
order_amount = 120.0
# 比较订单金额与免运费门槛,将比较结果 True 或 False 赋给变量 is_free_shipping
is_free_shipping = order_amount >= 99.0
print(is_free_shipping)

输出:

text
True

表达式 order_amount >= 99.0 的求值结果为 True,表明该笔订单符合免运费要求。

在实际业务中,我们不仅需要获取这个布尔值,更需要让程序根据该判断结果选择后续处理方案:当条件成立时,将运费调整为 0 元;当条件不成立时,维持原有的基础运费。Python 使用 if 语句控制是否执行特定的代码块。

案例:预设基础运费,当订单金额满足免运费门槛时将运费设置为零。

python
order_amount = 120.0  # 订单金额
shipping_fee = 6.0    # 预设基础运费(未达门槛时的标准收费)

# 如果订单金额 >= 99.0:
#     运费调整为 0.0
if order_amount >= 99.0:
    shipping_fee = 0.0

# 退出 if 代码块,继续主流程:实付金额 = 订单金额 + 运费
total = order_amount + shipping_fee
print(f"运费:{shipping_fee:.2f} 元")  # :.2f 为格式说明符,表示保留两位小数输出
print(f"实付金额:{total:.2f} 元")

输出:

text
运费:0.00 元
实付金额:120.00 元

分支结构(单 if 语句):代码与执行流程图

如上图所示,该程序的执行流程分为四个步骤:

  1. 变量初始化:将订单金额 order_amount 设为 120.0,将基础运费 shipping_fee 设为 6.0;
  2. 条件求值:执行到 if 语句时,解释器计算条件表达式 order_amount >= 99.0,得到布尔值 True;
  3. 进入if分支:由于条件为 True,程序进入 if 内部缩进的代码块,执行 shipping_fee = 0.0,将变量 shipping_fee 的值更新为 0.0;
  4. 汇合执行:离开 if 代码块恢复主流程,继续顺序执行后续语句 total = order_amount + shipping_fee。此时 total = 120.0 + 0.0 = 120.0,随后输出运费与实付金额。

提示(格式说明符 :.2f):在 f-string 中,花括号内的 :.2f 用于控制浮点数的显示格式。其中冒号 : 引导格式规则,.2 表示小数点后两位,f 表示浮点数。在涉及货币金额的业务场景中,通常需要对齐到“分”,使用 :.2f 可以将 0.0 规整显示为 0.00、120.0 显示为 120.00。注意,格式化只影响输出文本,不会改变变量本身的数值与类型。

若将商品金额改为 85.0,条件表达式 85.0 >= 99.0 的求值结果为 False。此时解释器直接跳过 if 代码块内部的所有语句,变量 shipping_fee 保留初始值 6.0。程序继续执行后续代码,计算得到 85.0 + 6.0 = 91.0。

订单金额 条件 order_amount >= 99.0 是否进入 if 代码块 最终运费 最终实付金额
85.0 元 False 否(跳过) 6.0 元 91.0 元
99.0 元 True 是(执行) 0.0 元 99.0 元
120.0 元 True 是(执行) 0.0 元 120.0 元

随堂练习:将 order_amount 依次修改为 98.0、99.0 与 100.0。先手动推算运费与实付总额,再运行代码核对。注意“达到 99 元”包含恰好 99 元的情形,因此使用 >= 而非 >。

4.1.2 冒号、缩进与代码块机制

if 语句的标准语法格式如下:

text
if 条件表达式:
    条件成立时执行的语句 1
    条件成立时执行的语句 2
    ...

分支结束后继续执行的后续语句

该结构包含四个要素:

  1. 关键字 if:声明一个条件分支;
  2. 条件表达式:紧跟在 if 之后是一个布尔值:可以是关系运算表达式(的运算结果)、逻辑表达式(的运算结果),也可以是布尔值变量; 〔提示 1〕
  3. 英文冒号 ::位于条件末尾,引出后续从属代码块;
  4. 缩进代码块(Indented Code Block):属于该分支的一组语句,统一向右缩进(PEP 8 推荐使用 4 个空格)。

当代码的缩进层级恢复到与 if 对齐时,即表示退出该分支代码块,重回主执行流程。

在上例中,属于 if 代码块的语句仅有 shipping_fee = 0.0 一行。因此,条件成立与否仅决定是否更新shipping_fee的值。后续计算 total 和打印输出的语句与 if 保持相同缩进层级,不从属于该条件,因此无论条件真假都会照常执行。

这类仅在条件成立时执行特定语句、条件不成立时直接跳过的结构,称为单分支结构(Single-Branch Structure)。

提示:判断两个变量或数值是否相等时,条件表达式中必须使用双等号 ==(如 status == "PAID"),不要误写为赋值运算符单个等号 =。

4.1.3 单个分支内的多条语句

一个代码块中可以包含多条语句(一个代码块)。只要保持相同的缩进层级,这些语句都会作为整体一并执行或一并跳过。

案例:订单金额满 200 元时立减 20 元,并向终端打印优惠提醒。

python
order_amount = 230.0
payment = order_amount  # 将应付金额预设为原价

# 如果订单金额 >= 200.0:
#     应付金额立减 20.0 元
#     输出优惠提示信息
if order_amount >= 200.0:
    payment = order_amount - 20.0
    print("已享受满 200 减 20 优惠")

# 退出 if 代码块,统一输出最终结算金额
print(f"应付金额:{payment:.2f} 元")

输出:

text
已享受满 200 减 20 优惠
应付金额:210.00 元

本例中,order_amount >= 200.0 为 True,程序进入 if 代码块,依次执行扣减 20 元与打印优惠提示两条语句。由于两条语句拥有完全相同的缩进,它们构成了同一个代码块。

若将 order_amount 修改为 180.0,条件为 False,解释器直接跳过该代码块内的两条语句,终端不会输出优惠提示,payment 保持初始值 180.0。

随堂练习:将 order_amount 修改为 180.0,先推测终端输出内容,再运行程序验证。


4.2 执行A还是执行B?if / else 结构

4.2.1 二选一的处理

现实中许多场景并非单纯的“做还是不做”,而是明确的“二选一”:

  • 账户余额充足时,扣款并提示支付成功;
  • 账户余额不足时,不能扣款,并提示支付失败。

面对这种非此即彼的选择,Python 提供了 if/else 双分支结构,让程序在两条互斥的路径中明确选择一条执行。

案例:根据账户余额是否充足,分别执行扣款成功或输出失败提示。

python
account_balance = 120.0  # 扣款前的账户余额
payment = 114.0          # 本次交易需支付的金额

# 如果账户余额 >= 支付金额:
#     扣除支付金额
#     提示支付成功
# 否则:
#     提示余额不足
if account_balance >= payment:
    account_balance = account_balance - payment
    print("支付成功")
else:
    print("余额不足,支付未完成")

# 退出 if/else 结构,继续主流程:统一输出最新余额
print(f"当前余额:{account_balance:.2f} 元")

输出:

text
支付成功
当前余额:6.00 元

分支结构(if-else 语句):代码与执行流程图

如上图所示,程序执行时首先计算判断条件 account_balance >= payment:

  • 当结果为 True 时:进入 if 对应的缩进代码块,扣除金额并输出“支付成功”。执行完毕后,解释器直接跳过 else 对应的代码块;
  • 当结果为 False 时:解释器跳过 if 代码块,直接进入 else 对应的代码块,输出“余额不足,支付未完成”。在此分支中,程序没有修改 account_balance,变量保留原值。

双分支结构最核心的特点是互斥:在任意一次执行中,解释器有且仅会走其中一条分支,绝不可能两条都走,也不可能两条都跳过。分支执行完毕后,程序汇合至后续语句,继续往下执行。

初始余额 支付金额 条件求值 执行分支 扣款后余额
120.0 元 114.0 元 True if 分支(扣款成功) 6.0 元
100.0 元 114.0 元 False else 分支(扣款失败) 100.0 元
114.0 元 114.0 元 True if 分支(扣款成功) 0.0 元

4.2.2 语法对齐与 else 的使用规则

双分支结构的标准书写格式如下:

text
if 条件表达式:
    条件成立时执行的语句
    ...
else:
    条件不成立时执行的语句
    ...

分支结束后继续执行的后续语句

书写时需要注意两项规则:

  1. 缩进对齐:else 必须与配对的 if 保持在完全相同的缩进层级(最左侧对齐),表明它们是一组二选一决策;
  2. else 后面不能写判断条件:else 后面直接写英文冒号 :,不能附加任何条件表达式。因为 else 本身就代表“上述条件不成立(为 False)时的兜底选择”,不需要也不允许重复写条件。

4.2.3 两种常见写法:预设默认值 vs 明确双分支

在 4.1.1 节中,我们计算运费采用了“预设默认值 + 单分支条件修改”的写法:

python
shipping_fee = 6.0  # 预设基础运费

# 如果订单金额 >= 99.0:
#     运费修改为 0.0
if order_amount >= 99.0:
    shipping_fee = 0.0

同样的需求也可以使用 if/else 双分支结构来实现:

python
order_amount = 85.0

# 如果订单金额 >= 99.0:
#     运费确定为 0.0(免运费)
# 否则:
#     运费确定为 6.0(标准运费)
if order_amount >= 99.0:
    shipping_fee = 0.0
else:
    shipping_fee = 6.0

print(f"运费:{shipping_fee:.2f} 元")

输出:

text
运费:6.00 元

对比这两种写法:

  • 预设默认值写法(单分支):适合“大多数情况下保持默认值,只有极少数特殊情况才需要修改”的场景,代码行数更少、更轻巧;
  • 明确双分支写法(if/else):在两条分支中分别显式赋值。这种写法对称工整,无论走哪条分支,变量的赋值都一目了然,读者不需要回头翻看前面有没有预设值。在复杂的业务逻辑中,这种写法往往更清晰、不易出错。

随堂练习:在支付案例中,将 account_balance 分别修改为 100.0 和 114.0,预测终端打印的两行文本,再运行验证。


4.3 面对多种情况:if/elif/else 多分支结构

4.3.1 划分多个等级与 elif 关键字

当现实中的规则包含三个或更多互斥区间时,单纯的 if/else 就不够用了。例如高校课程成绩的评定规则:

成绩区间 〔提示 5〕 评定等级
90 分及以上 优秀
80 分至 89 分 良好
60 分至 79 分 及格
60 分以下 不及格

一个具体的成绩值只能归属于上述四个等级中的某一个。Python 使用 if/elif/else 多分支结构来表达这种“多选一”的决策。

elif 是 else if 的合并缩写,意为“否则,如果……”。它的核心逻辑是:只有前面的条件都不成立时,才来检查当前的条件。

案例:输入百分制课程成绩,输出对应的评级结果。

python
score = 85

# 如果成绩 >= 90:
#     等级为 "优秀"
# 否则如果成绩 >= 80:
#     等级为 "良好"
# 否则如果成绩 >= 60:
#     等级为 "及格"
# 否则:
#     等级为 "不及格"
if score >= 90:
    grade = "优秀"
elif score >= 80:
    grade = "良好"
elif score >= 60:
    grade = "及格"
else:
    grade = "不及格"

print(f"成绩:{score} 分,等级:{grade}")

输出:

text
成绩:85 分,等级:良好

4.3.2 从上到下逐个检查,命中即退出

对于输入数据 score = 85,解释器的具体执行步骤如下:

判定次序 条件表达式 求值结果 动作
1 score >= 90 False 不满足,跳过当前分支,检查下一个条件
2 score >= 80 True 满足条件,执行 grade = "良好",立即结束整组分支
3 score >= 60 未求值 跳过(不再计算)
4 else 未执行 跳过

多分支结构的运行原则非常明确:从上到下逐个检查,一旦命中立刻退出:

  1. 解释器从第一行 if 开始,从上到下依次判断每个条件的真假;
  2. 一旦遇到某个条件计算为 True,立刻进入该条件对应的缩进代码块执行;
  3. 执行完毕后,整组分支结构彻底结束,解释器直接跳过后面所有的 elif 和 else代码块。

很多初学者会习惯性写成 elif score >= 80 and score < 90:。这种写法虽然逻辑上没有错,但完全是多余的。因为能走到 elif score >= 80 这一行,前提就是上一行的 score >= 90 已经失败了(即已经排除了 90 分及以上的情形)。因此直接写 score >= 80 即可,代码既干净又不会产生冗余。

4.3.3 条件顺序的重要性

在处理连续数值区间时,条件的书写顺序直接决定了程序逻辑是否正确。

“由严到宽”准则的本质,在于各个条件的判定范围存在相互包含关系。在正文的单侧下界判断中(score >= 90、score >= 80、score >= 60):

  • 数轴上的子集关系:更严格条件的解集是更宽松条件解集的真子集,在集合上表现为逐层包含:
    [90, 100] \subset [80, 100] \subset [60, 100]
  • 高分覆盖特性:高分数值同时满足所有这些条件。例如 95 分不仅满足 \ge 90,也同时满足 \ge 80 与 \ge 60。

若颠倒顺序、将较宽的条件写在最上方,就会引发逻辑覆盖错误:

python
# 错误示范:颠倒了判断顺序(由宽到严)
# 若将宽松条件(>= 60)置于最上方,高分会被及格分支提前拦截:
if score >= 60:
    grade = "及格"
elif score >= 80:
    grade = "良好"
elif score >= 90:
    grade = "优秀"

if/elif/else 结构具有单路匹配即终止的特性。当 score = 95 时,首个条件 score >= 60 会在第一时间命中,程序直接将 grade 赋值为 "及格" 并跳出整组分支,导致下方更严格的“优秀”分支永远无法被执行。

排列原则:

在条件范围相互包含的场景中,多分支结构必须遵循“先判断更严格的条件,再判断更宽松的条件”(由严到宽) 〔提示 3〕:

  • 采用 >= 判断:数值越大,条件越严格,覆盖范围越小。必须从大到小依次递减排列(先排查集合最小、最严格的 90,再排查 80,最后排查范围最宽的 60);
  • 采用 <= 判断:数值越小,条件越严格,覆盖范围越小。必须从小到大依次递增排列(先排查较小阈值,再逐步放宽至较大阈值)。

随堂练习:将 score 分别设置为 59、60、79、80、89 与 90,先推算对应的评级,再运行验证边界值处理是否精确。


4.4 同时判断多个条件:逻辑运算 and、or、not

在实际生活中,我们做决定往往需要同时考虑多个条件。比如:“明天不下雨并且温度在 20 度以上,我们去爬山”;或者“凭学生证或者老年卡,可以享受半价门票”。

在 Python 中,当单靠一个比较运算符(如 >、<、==)无法表达复杂的判断逻辑时,我们使用逻辑运算符将多个条件组合起来。常用的逻辑运算符有三个:and(并且)、or(或者)、not(取反)。

4.4.1 and 运算:条件必须同时满足

只有当左右两边同时为 True 时,and 的运算结果才为 True。只要其中任何一边为 False,整个结果就是 False。 〔提示 2〕

案例:店铺规定只有当客户是 VIP 会员且订单金额满 100 元时,才能享受 20 元立减。

python
order_amount = 120.0
is_vip = True

# 如果订单金额 >= 100 并且是 VIP 会员:
#     减免金额设置为 20.0
# 否则:
#     减免金额设置为 0.0
if order_amount >= 100.0 and is_vip:
    discount = 20.0
else:
    discount = 0.0

# 支付金额 = 订单金额 - 减免金额
payment = order_amount - discount
print(f"优惠金额:{discount:.2f} 元")
print(f"实付金额:{payment:.2f} 元")

输出:

text
优惠金额:20.00 元
实付金额:100.00 元

提示

这里的 is_vip 本身就是布尔值(True 或 False)。直接写 if order_amount >= 100.0 and is_vip: 就足够清晰,不需要写成 is_vip == True。

4.4.2 or 运算符:满足任意一个条件即可

只要左右两边的条件中有任意一个为 True,or 的运算结果就为 True。只有当两边全部为 False 时,结果才为 False。 〔提示 2〕

案例:订单金额满 99 元或者客户是 VIP 会员,即可享受免运费。

python
order_amount = 85.0
is_vip = True

# 如果订单金额 >= 99 或者属于 VIP 会员:
#     运费为0.0
# 否则:
#     运费为6.0
if order_amount >= 99.0 or is_vip:
    shipping_fee = 0.0
else:
    shipping_fee = 6.0

print(f"运费:{shipping_fee:.2f} 元")

输出:

text
运费:0.00 元

在这段代码中,虽然 order_amount >= 99.0 是 False(只买了 85 元),但因为 is_vip 是 True,or 的整体计算结果依然是 True,顺利享受了免运费。

我们可以用一张真值表直观对比 and 与 or 的差异:

条件 A 条件 B A and B(必须全真才为真) A or B(有一真即为真)
False False False False
True False False True
False True False True
True True True True

4.4.3 条件取反与运算优先级:not 与圆括号

逻辑非运算符 not 用于把一个布尔值“颠倒”过来:

  • not True 变成 False
  • not False 变成 True

当一个判断中同时出现多个运算符时,Python 会按照既定的优先级顺序来执行:

\text{算术运算} > \text{比较运算} > \text{not} > \text{and} > \text{or}

也就是说,先做加减乘除,再做比较大小,接着做 not,然后做 and,最后做 or。 〔提示 4〕

但与四则运算相同,圆括号()具有最高优先级。在编写复合条件时,使用圆括号 () 把需要优先运算的条件括起来。这样不仅能确保代码按照你期望的顺序执行,还能让任何阅读你代码的人一目了然。 〔提示 4〕

案例:订单金额满 99 元或为 VIP 会员可免运费,但偏远地区除外。

python
order_amount = 85.0
is_vip = True
is_remote_area = False  # 是否为偏远地区(False 表示不是偏远地区)

# 如果(订单金额 >= 99 或者属于 VIP 会员)并且不是偏远地区:
#     运费为 0.0
# 否则:
#     运费为 6.0
if (order_amount >= 99.0 or is_vip) and not is_remote_area:
    shipping_fee = 0.0
else:
    shipping_fee = 6.0

print(f"运费:{shipping_fee:.2f} 元")

输出:

text
运费:0.00 元

4.4.4 常见误区:漏写比较对象

在判断一个变量是否等于多个可能值时,如果顺着日常口语习惯写出类似这样的代码:

python
# 错误示范!不要这样写!
if coupon == "SAVE10" or "VIP10":
    ...

很多人以为这句代码的意思是“如果优惠券是 SAVE10 或者 VIP10”。但实际上,这会导致严重的逻辑漏洞:

  1. Python 会把这一行拆成两半来算:左边是 coupon == "SAVE10",右边是单独的字符串 "VIP10";
  2. 如果优惠券不对,左边判定为 False;接着执行 False or "VIP10";
  3. 在 Python 中,任何非空字符串被当作布尔值看待时,结果为真(Truthiness);
  4. 于是,False or "VIP10" 的最终结果变成了真!无论客户输入什么优惠券,甚至哪怕随便乱打一串字符,这个判断都会通过! 〔提示 2〕

正确写法:or 两边必须各自写出完整的比较表达式,指定谁跟谁比较:

python
coupon_code = "VIP10"

# 如果优惠码是 "SAVE10" 或者优惠码是 "VIP10":
#     提示有效
# 否则:
#     提示无效
if coupon_code == "SAVE10" or coupon_code == "VIP10":
    print("优惠码验证有效")
else:
    print("优惠码无效")

输出:

text
优惠码验证有效

此外,在表达区间范围时,Python 允许使用符合数学直觉的 连续比较(Chained Comparisons) 语法:

python
# 形式一:显式用 and 连接
# 如果分数 >= 60 并且分数 < 80:
if score >= 60 and score < 80:
    ...

# 形式二:Python 链式比较(更符合数学直觉,推荐!)
# 如果 60 <= 分数 < 80:
if 60 <= score < 80:
    ...

以上两种形式等价。

4.4.5 逻辑运算的短路机制

当 Python 计算包含 and 或 or 的复合条件时,例如

Python
A and B and C and D
# A, B, C, D均为关系运算表达式或者布尔值

Python 会从左到右依次计算A, B, C, D,一旦左边的结果已经能够确定整个表达式的真假,解释器就会立即停止计算后续的表达式。这种机制叫做短路求值(Short-Circuit Evaluation)。

短路机制非常直观:

  1. and 的短路机制:因为 and 要求两边必须同时为真,所以只要 and的左边是 False,整个表达式无论右边是什么都必定是 False 了。此时 Python 会立刻返回False, and的右边不会被执行。
  2. or 的短路规则:因为 or 只要有一边为真就是真,所以只要 or的左边是 True,整个表达式无论右边是什么都必定是 True 了。此时 Python 立即返回True,or的右边直接被跳过。

短路规则可以总结为下表:

表达式 左边结果 是否计算右边? 原因
A and B False 否(短路跳过) and 要求两边均为真;左边为假,整个表达式必定为假
A and B True 是(继续计算) 左边虽为真,但整个表达式的真假仍取决于右边
A or B True 否(短路跳过) or 只要一边为真即为真;左边为真,整个表达式必定为真
A or B False 是(继续计算) 左边虽为假,但若右边为真,整个表达式仍可为真

短路机制不仅能避免多余的计算,还能用来编写安全的防御性代码(Defensive Programming)。最常见的应用是:在执行可能出错的计算之前,先在左边放一个检查条件。

案例:计算平均成绩。如果科目数为 0,直接除以科目数会导致“除以零”错误,使程序报错中断。我们可以利用 and 的短路机制先检查科目数。

python
total_score = 150.0
course_count = 0

# 利用 and 短路机制:
# 科目数为 0 时直接判定为 False,右边的除法不会被执行,避免除以零错误
if course_count > 0 and (total_score / course_count) >= 60.0:
    print("考核达标")
else:
    print("无有效成绩或考核未达标")

输出:

text
无有效成绩或考核未达标

在这个例子中,因为 course_count = 0:

  1. Python 先计算左边的 course_count > 0,得到 False;
  2. 触发 and 的短路机制,右边的除法 (total_score / course_count) >= 60.0 被直接跳过,不会被执行;
  3. 程序避免了“除以零”错误,安全执行 else 分支。
注意

多个条件的前后顺序很重要
短路机制严格遵循从左到右的计算顺序。如果把两边的条件顺序颠倒:
if (total_score / course_count) >= 60.0 and course_count > 0:
Python 会先计算左边的除法。当 course_count 为 0 时,程序会因除以零错误立即报错中断(抛出 ZeroDivisionError: float division by zero)。因此,必须把起保护作用的检查条件写在左边,把依赖该条件的计算写在右边。


4.5 连续多个独立 if 与 if/elif 有什么区别?

很多初学者容易搞混:“连续写多个独立的 if”与“写一组 if/elif/else”到底有什么区别? 它们控制的业务逻辑完全不同。

4.5.1 条件及业务规则相互独立:连续写多个独立 if

假设店铺搞促销活动:

  • 订单金额满 100 元,赠送一张优惠券;
  • 订单金额满 200 元,额外赠送一个定制帆布袋;
  • 这两项赠品可以同时获得(两个条件及业务规则相互独立,互不影响)。

如果一位顾客消费了 250 元,这两项奖励他都达标了。此时我们应当使用多个独立的 if 语句分别去判断:

python
order_amount = 250.0

# 规则 1:如果订单金额 >= 100,赠送一张优惠券
if order_amount >= 100.0:
    print("赠送一张优惠券(满 100 元奖励)")

# 规则 2:如果订单金额 >= 200,赠送一个定制帆布袋(独立判断,不受规则 1 影响)
if order_amount >= 200.0:
    print("赠送一个定制帆布袋(满 200 元奖励)")

输出:

text
赠送一张优惠券(满 100 元奖励)
赠送一个定制帆布袋(满 200 元奖励)

每一个独立的 if 都是一个互不相干的条件检查点。无论第一个 if 是不是满足,程序都会接着去检查第二个 if(两个if代码块符合顺序结构)。

4.5.2 条件互斥多选一:使用 if/elif 分支链

如果店铺修改了规则:“满 200 元只送帆布袋;满 100 元但不足 200 元只送优惠券”——也就是这两项奖励互斥,不能兼得。

这时候如果还写两个独立的 if,消费 250 元的顾客就会同时收到两个礼品,这就违反了店规。正确的做法是使用 if/elif 分支链:

python
order_amount = 250.0

# 如果订单金额 >= 200:
#     赠送一个定制帆布袋
# 否则如果订单金额 >= 100:
#     赠送一张优惠券
if order_amount >= 200.0:
    print("赠送一个定制帆布袋")
elif order_amount >= 100.0:  # 只有在不满 200 元的前提下,才会检查这个条件
    print("赠送一张优惠券")

输出:

text
赠送一个定制帆布袋
业务规则特点 推荐使用的控制结构 核心特点 生活与工程实例
规则互相独立,可以叠加 多个独立的 if 每个条件分别判断,可以同时触发多个结果 购物满额送多重好礼、用户注册表单的多项字段格式校验
规则互相排斥,只能选其一 一组 if/elif/else 一旦命中前面的分支就立即跳出,最多只执行一个分支 考试成绩等级评定、快递重量阶梯计费、会员排他折扣

4.6 if 语句的嵌套:多层条件判断

4.6.1 先满足大前提,再做细化判断

在很多生活场景中,我们做决定的过程往往是环环相扣的:后一个判断必须要以前一个判断的成立为前提。

例如电商网站展示商品价格的流程:

  1. 首先看用户有没有登录;
  2. 如果已经登录,再进一步看他是不是 VIP 会员:VIP 会员展示 80 元特权价,普通用户展示 100 元原价;
  3. 如果根本就没登录,那就不需要去判断会员身份了,直接提示“请先登录”。

在一个 if 或 else 的内部缩进代码块里,再写一个完整的 if 语句,就叫做嵌套分支(Nested Conditionals)。

案例:通过嵌套分支判断登录状态与会员等级。

python
is_logged_in = True  # 用户是否登录
is_vip = False       # 用户是否为会员

# 如果用户已登录:
#     如果用户是 VIP 会员:
#         价格设置为 80.0
#     否则:
#         价格设置为 100.0
#     输出商品价格
# 否则:
#     提示请先登录
if is_logged_in:

    # -----------嵌套内层 if 代码块开始----------
    # 前置条件:is_logged_in为True
    if is_vip:
        price = 80.0
    else:
        price = 100.0
    # -----------嵌套内层 if 代码块结束----------

    # 继续执行外层 if 代码块的后续语句
    print(f"商品价格:{price:.2f} 元")
else:   # is_logged_in为False
    print("请先登录后查看价格")

输出:

text
商品价格:100.00 元

4.6.2 缩进决定 else 与哪个 if 配对

在 Python 中,else 属于哪一个 if,完全由缩进决定:

  • 缩进 0 个空格(顶格)的 else:,跟最外层缩进 0 个空格的 if is_logged_in: 对齐,表示“未登录”时的处理分支;
  • 缩进 4 个空格的 else:,跟内层缩进 4 个空格的 if is_vip: 对齐,表示“已登录但不是 VIP”时的处理分支。

Python 嵌套分支结构流程图

从上图的两层判断与分支执行流向可以看出:

  1. 分支 ①(未登录):外层判断 is_logged_in 为 False 时,流程直接走向外层 else,执行 print("请先登录后查看价格")。整个内层代码块被完全跳过,内层的 is_vip 根本不会被求值;
  2. 分支 ②(已登录普通用户):外层判断为 True 从而进入内层,内层判断 is_vip 为 False,走向内层 else 设定标准单价(100.0 元),随后统一汇合输出价格;
  3. 分支 ③(已登录 VIP 会员):外层与内层判断均为 True,走向内层 if 设定会员专享价(80.0 元),随后统一汇合输出价格。

编程建议:虽然 Python 允许在分支结构中继续嵌套 if 语句,但当嵌套层数达到 3 层及以上时,代码会持续向右缩进(常被称为“箭头型代码”),阅读和理解的成本会明显增加。在 4.9 节中,我们将学习如何利用函数和提前返回(卫语句)来展开多层嵌套,使代码结构更平整、更易读。


4.7 在函数中结合分支判断

4.7.1 根据不同输入选择计算分支

在第二章中,我们学习了函数的“输入-处理-输出”流程。当我们在函数体内引入 if 语句时,函数便能够根据传入的参数,在不同的计算路径之间自动做出选择。

以第二章课后习题中的快递运费计费规则为例(计费重量为正整数,单位为公斤):

  • 不超过 1 公斤(首重):重量在 1 公斤及以内,统一收取首重运费 12 元;
  • 超过 1 公斤(续重):超出部分每超出 1 公斤加收 5 元,计算公式为 12 + (\text{weight} - 1) \times 5。

案例:编写包含分支判断的运费计算函数,兼顾首重与续重两种计费方式。

python
def calculate_freight(weight: int) -> int:
    """根据快递重量计算运费。

    参数:
        weight (int): 计费重量(正整数,公斤)
    返回:
        int: 计算得出的运费金额(元)
    """
    # 1 公斤及以内(首重,不满 1 公斤亦按 1 公斤计):
    if weight <= 1:
        freight = 12
    else:
        # 超过 1 公斤(续重):每超出 1 公斤加收 5 元
        freight = 12 + (weight - 1) * 5

    # 分支执行完毕后,统一返回最终计算出来的运费
    return freight


# 调用函数并输出测试结果
print(calculate_freight(1))  # 1 公斤 -> 首重 12 元
print(calculate_freight(2))  # 2 公斤 -> 12 + 1*5 = 17 元
print(calculate_freight(4))  # 4 公斤 -> 12 + 3*5 = 27 元

输出:

text
12
17
27

4.7.2 统一返回结果:单出口结构

观察该函数的执行过程,可以分为三个清晰的步骤:

  1. 接收输入:形参 weight 接收调用方传入的具体数值;
  2. 分支计算:if/else 根据 weight <= 1 的判断结果,在对应分支中为变量 freight 赋值;
  3. 统一返回:return freight 位于分支结构之外(与 if 同级缩进)。无论程序执行了哪一条分支,变量 freight 都已经得到明确的计算结果,最后由这一行统一返回给调用方。

这种在分支结构外部只保留一行 return 的写法,被称为单出口结构(Single Exit Point)。

随堂练习:调用 calculate_freight(3) 与 calculate_freight(5),先推算预期运费,再在终端中打印验证(预期返回值分别为 22 与 32)。


核心内容练习

在实际编程中,我们通常需要先理解具体问题,再自主设计函数的名称、参数、类型标注与返回值。请根据以下各题的要求完成函数设计与编写,并使用文中的测试用例进行自测。

练习 1:外卖订单实付金额计算

某外卖平台的配送费规则如下:

  1. 标准配送费为 5.0 元;
  2. 当餐品总额达到 30.0 元及以上,或下单用户为 VIP 会员时,免收配送费(计 0.0 元);
  3. 订单实付金额为餐品总额与配送费之和。

测试用例(供自测参考):

测试用例 餐品金额(元) 是否为 VIP 会员 期望返回值 说明
用例 1 25.0 False 30.0 不满 30 元且非会员,加收 5.0 元配送费
用例 2 30.0 False 30.0 刚好满 30 元,免收配送费
用例 3 25.0 True 25.0 不满 30 元但为 VIP 会员,免收配送费

练习 2:阶梯打印费用计算

某自助打印店实行阶梯计价,打印页数越多,单页单价越优惠:

打印页数(正整数) 单页单价 计费区间说明
不满 20 页 0.5 元 / 页 1 \le \text{pages} < 20
20 页至 49 页 0.4 元 / 页 20 \le \text{pages} \le 49
50 页及以上 0.3 元 / 页 \text{pages} \ge 50

总费用等于打印页数乘以对应的单页单价。

测试用例(供自测参考):

测试用例 打印页数 期望返回值 说明
用例 1 19 9.5 第一阶梯上限:19 \times 0.5 = 9.5 元
用例 2 20 8.0 第二阶梯下限:20 \times 0.4 = 8.0 元
用例 3 49 19.6 第二阶梯上限:49 \times 0.4 = 19.6 元
用例 4 50 15.0 第三阶梯下限:50 \times 0.3 = 15.0 元

练习 3:订单支付状态分层检查

在线支付系统在处理支付请求时,需要依次检查订单状态与账户余额:

  1. 检查订单状态:若订单已被取消,返回提示 "订单已取消";
  2. 检查账户余额:若订单未取消,继续检查账户余额是否足够支付:
    • 余额大于或等于应付金额时,返回提示 "可以支付";
    • 余额小于应付金额时,返回提示 "余额不足"。

要求:

  • 自主设计函数名、参数名(含类型标注)与返回值类型(字符串 str);
  • 使用两层嵌套分支结构(嵌套 if/else)实现,在外层 else 块内嵌套对余额的判断;
  • 采用单出口结构,使用变量记录提示文本,并在函数末尾统一返回。

测试用例(供自测参考):

测试用例 订单是否已取消 账户可用余额(元) 应付金额(元) 期望返回值 说明
用例 1 True 120.0 114.0 "订单已取消" 订单已取消,无需比对余额
用例 2 False 120.0 114.0 "可以支付" 订单未取消且余额充足
用例 3 False 100.0 114.0 "余额不足" 订单未取消但余额不足
用例 4 False 114.0 114.0 "可以支付" 边界情况:余额恰好等于应付金额

参考答案与编程指引

练习 1:外卖订单实付金额计算

python
def calculate_takeout_total(food_total: float, is_vip: bool) -> float:
    """根据餐品金额与会员身份计算外卖订单实付总额。

    参数:
        food_total (float): 餐品总金额(元)
        is_vip (bool): 下单用户是否为 VIP 会员
    返回:
        float: 包含配送费的最终实付金额(元)
    """
    # 餐品满 30 元或为 VIP 会员,免收配送费
    if food_total >= 30.0 or is_vip:
        delivery_fee = 0.0
    else:
        delivery_fee = 5.0

    # 单出口返回:实付总额 = 餐品金额 + 配送费
    return food_total + delivery_fee


# 测试调用
print(calculate_takeout_total(25.0, False))  # 输出: 30.0
print(calculate_takeout_total(30.0, False))  # 输出: 30.0
print(calculate_takeout_total(25.0, True))   # 输出: 25.0

编程指引:

  1. 函数接口设计:函数名推荐采用动宾短语(如 calculate_takeout_total),直接表明函数的作用。布尔型形参通常以 is_ 开头命名(如 is_vip),表示判断某种状态。由于金额涉及小数运算,餐品金额与返回值均应标注为 float 类型。
  2. 条件组合与逻辑运算:免收配送费的两个条件满足其一即可,因此使用逻辑运算符 or 组合条件(food_total >= 30.0 or is_vip)。
  3. 单出口结构:在分支语句内部只负责为变量 delivery_fee 赋值,在分支外部统一计算 food_total + delivery_fee 并返回。这样能将“条件判断”与“费用计算”两步拆开,避免在每个分支中重复书写相同的加法运算。

练习 2:阶梯打印费用计算

python
def calculate_printing_fee(pages: int) -> float:
    """根据打印总页数按阶梯标准计算应收费用。

    参数:
        pages (int): 打印总页数(正整数)
    返回:
        float: 应收总费用(元)
    """
    # 遵循由严到宽(从大到小)的原则匹配单页单价
    if pages >= 50:
        unit_price = 0.3
    elif pages >= 20:
        unit_price = 0.4
    else:
        unit_price = 0.5

    # 统一计算并返回总费用
    return pages * unit_price


# 测试调用
print(calculate_printing_fee(19))  # 输出: 9.5
print(calculate_printing_fee(20))  # 输出: 8.0
print(calculate_printing_fee(49))  # 输出: 19.6
print(calculate_printing_fee(50))  # 输出: 15.0

编程指引:

  1. 函数接口设计:打印页数是整数,形参类型标注为 int。单价为小数(0.3、0.4、0.5),页数乘以单价的结果是浮点数,因此返回值类型标注为 float。
  2. 多分支条件的排列顺序:使用 >= 比较区间时,数值越大,限制越严格。必须先判断最严格的 pages >= 50,再判断次严格的 pages >= 20,最后用 else 涵盖不满 20 页的情形。
  3. 因果衔接与避免冗余:程序能执行到 elif pages >= 20,说明前一个条件 pages >= 50 不成立,此时已经天然满足了 pages < 50 的前提。因此只需写 pages >= 20,不必重复书写 pages < 50 and pages >= 20。

练习 3:订单支付状态分层检查

python
def get_payment_message(
    is_cancelled: bool, account_balance: float, payment: float
) -> str:
    """分层检查订单状态与账户余额,返回支付提示文本。

    参数:
        is_cancelled (bool): 订单是否已被取消
        account_balance (float): 账户当前可用余额(元)
        payment (float): 本次订单应支付金额(元)
    返回:
        str: 支付提示文本
    """
    # 外层:优先检查订单是否已取消
    if is_cancelled:
        message = "订单已取消"
    else:
        # 内层:订单未取消时,进一步比较余额是否充足
        if account_balance >= payment:
            message = "可以支付"
        else:
            message = "余额不足"

    return message


# 测试调用
print(get_payment_message(True, 120.0, 114.0))   # 输出: 订单已取消
print(get_payment_message(False, 120.0, 114.0))  # 输出: 可以支付
print(get_payment_message(False, 100.0, 114.0))  # 输出: 余额不足
print(get_payment_message(False, 114.0, 114.0))  # 输出: 可以支付

编程指引:

  1. 分层逻辑的因果关系:如果订单已经取消,后续比较余额就没有任何意义。因此先在外层排除已取消的订单,把余额判断放在外层 else(订单正常有效)代码块内。
  2. 缩进与从属关系:内层的 if/else 相对外层 else 整体向右缩进 4 个空格,代码结构清晰表达了“只有在订单未取消的前提下,才进一步检查余额”的逻辑。
  3. 单出口结构:在各个分支中为变量 message 赋予不同的提示字符串,最后在函数末尾统一返回。函数的出口唯一,逻辑自然清晰。

第二部分:扩展知识点

4.8 条件表达式:单行实现二选一赋值

在前文的例子中,if/else 都是多行语句结构。如果业务逻辑非常简单——只是为了根据某个条件在两个备选值中选出一个赋给变量,写成多行代码有时显得有些繁琐。

Python 为此提供了一种更紧凑的语法形式——条件表达式(Conditional Expression),在其他编程语言中通常被称为三元运算符(Ternary Operator)。

4.8.1 条件表达式的语法与求值

条件表达式的基本语法结构如下:

text
目标变量 = 值1 if 条件 else 值2

它的求值顺序非常直观:

  1. 首先计算中间的 条件;
  2. 如果条件为 True,取左侧的 值1;
  3. 如果条件为 False,取右侧的 值2。

回顾 4.1.1 节中的免运费计算,多行分支的写法是:

python
if order_amount >= 99.0:
    shipping_fee = 0.0
else:
    shipping_fee = 6.0

改用条件表达式后,可以简练地写为单行:

python
order_amount = 120.0

# 订单金额达到 99.0 时运费为 0.0,否则为 6.0
shipping_fee = 0.0 if order_amount >= 99.0 else 6.0
print(f"运费:{shipping_fee:.2f} 元")

输出:

text
运费:0.00 元

4.8.2 使用建议

条件表达式能够精简代码行数,但使用时应以代码清晰为第一原则:

  • 推荐使用的场景:简单的“二选一”赋值,或者作为实参直接传进 print() 等函数调用中(如 print("及格" if score >= 60 else "不及格"));
  • 不建议使用的场景:分支内需要进行多步计算、输出打印或修改多个变量;此外,切忌将多个条件表达式串联嵌套(例如 a if c1 else b if c2 else c),这种写法会严重降低代码的可读性。当判断逻辑变得复杂时,使用标准的 if/elif/else 多行分支结构才是更清晰的选择。

4.9 利用 return 提前退出函数:卫语句写法

在 4.7 节中,我们介绍了单出口结构(在函数末尾保留单一 return)。但在实际编程中,如果函数需要排查多种异常情况,或者存在多层分支嵌套,坚持单出口往往会导致缩进层次逐层加深。

在 Python 中,函数一旦执行到 return 语句,便会立即结束运行并将结果返回给调用方,其后的代码不再执行。利用这一特性,我们可以在检测到不符合前置条件或异常情况时,直接使用 return 提前退出。这种编写方式被称为卫语句(Guard Clauses),也常被称为提前返回(Early Return)。

4.9.1 嵌套结构与提前返回的对比

以练习 3 中的 get_payment_message 为例,对比两种不同的编码风格:

风格一:传统嵌套结构(单出口结构)

python
def get_payment_message(
    is_cancelled: bool, account_balance: float, payment: float
) -> str:
    # 外层判断:检查订单是否已取消
    if is_cancelled:
        message = "订单已取消"
    else:
        # 内层判断:订单未取消时,检查余额是否足够
        if account_balance >= payment:
            message = "可以支付"
        else:
            message = "余额不足"
    return message

风格二:利用 return 提前退出(卫语句写法)

python
def get_payment_message(
    is_cancelled: bool, account_balance: float, payment: float
) -> str:
    # 卫语句 1:如果订单已取消,提前退出
    if is_cancelled:
        return "订单已取消"

    # 卫语句 2:如果余额足够支付,提前退出
    if account_balance >= payment:
        return "可以支付"

    # 默认返回:余额不足
    return "余额不足"


# 测试调用
print(get_payment_message(True, 120.0, 114.0))   # 触发第 1 条 return:订单已取消
print(get_payment_message(False, 120.0, 114.0))  # 触发第 2 条 return:可以支付
print(get_payment_message(False, 100.0, 114.0))  # 触发第 3 条 return:余额不足

输出:

text
订单已取消
可以支付
余额不足

4.9.2 卫语句的执行过程与优点

卫语句的执行逻辑非常清晰:

  1. 逐层排查的执行逻辑:
    • 当 is_cancelled = True 时,程序进入首个 if,遇到 return "订单已取消"。此时函数立即返回结果并结束运行,后面的代码完全不会执行;
    • 程序能够执行到第二个 if,前提是前一个条件没有成立(即 is_cancelled 必然为 False)。因此,无需再用 else 将后续代码缩进包裹起来;
    • 如果第二个条件同样不满足,程序便自然走到末尾的默认返回 return "余额不足"。
  2. 代码结构上的优点:
    • 消除多层缩进:所有的判断逻辑都在最外层平铺展开,代码从上到下一目了然;
    • 提前排除特殊情况:优先把不符合条件的情况拦截并返回,让函数的主干逻辑更加集中和突出。

扩展知识点练习

练习 1:景点门票价格计算(条件表达式)

某景区的门票定价规则如下:全价门票为 80.0 元;持有学生证的游客享受半价优惠,票价为 40.0 元。

要求:

  • 自主设计函数名、参数名(含类型标注)与返回值类型;
  • 使用 Python 的单行条件表达式(值1 if 条件 else 值2)返回计算结果,不使用多行 if/else;
  • 编写规范的文档字符串。

测试用例(供自测参考):

测试用例 是否持有学生证 期望返回值 说明
用例 1 True 40.0 学生身份,享受半价优惠
用例 2 False 80.0 非学生身份,按全价收费

练习 2:优惠券领取资格判断(嵌套分支与卫语句重构)

某电商平台发放优惠券的规则如下:

  1. 用户必须处于登录状态;未登录用户一律不能领券;
  2. 在已登录的前提下,满足以下任意一项即可领券:
    • 用户为 VIP 会员;
    • 订单金额达到 100.0 元及以上;
  3. 已登录但既不是 VIP、订单金额又不足 100.0 元的用户不能领券。

要求:

  • 自主设计函数名、参数名(含类型标注)与返回值类型(布尔型 bool);
  • 分别用两种方式实现该逻辑:
    1. 方式一(传统嵌套分支):外层判断登录状态,内层判断是否满足 VIP 或金额门槛;使用变量记录结果,并在函数末尾统一返回(单出口结构);
    2. 方式二(卫语句结构):使用 return 提前退出。先排除未登录的情况,再判断满足领券条件的情况,最后返回默认结果;
  • 编写测试代码,核对两种实现对于各组输入用例的返回值是否完全一致。

测试用例(供自测参考):

测试用例 是否已登录 是否为 VIP 订单金额(元) 期望返回值 说明
用例 1 False True 120.0 False 未登录,不能领券
用例 2 True False 80.0 False 已登录,但非 VIP 且金额不足 100 元
用例 3 True True 80.0 True 已登录且为 VIP,可以领券
用例 4 True False 120.0 True 已登录且金额达到 100 元,可以领券
用例 5 True False 100.0 True 边界情况:金额恰好满 100 元

参考答案与编程指引

练习 1:景点门票价格计算(条件表达式)

python
def get_ticket_price(is_student: bool) -> float:
    """根据学生身份计算门票价格(单行条件表达式实现)。

    参数:
        is_student (bool): 游客是否持有有效学生证件
    返回:
        float: 应付门票价格(元)
    """
    return 40.0 if is_student else 80.0


# 测试调用
print(get_ticket_price(True))   # 输出: 40.0
print(get_ticket_price(False))  # 输出: 80.0

编程指引:

  1. 函数接口设计:函数名推荐采用动宾短语(如 get_ticket_price),表明获取票价。形参 is_student 为布尔类型,返回值标注为浮点型 float。
  2. 条件表达式的求值机制:语句 40.0 if is_student else 80.0 在执行时,先判断中间的条件 is_student。若为真则取左边的 40.0,若为假则取右边的 80.0。在单纯二选一取值的场景下,使用条件表达式配合 return,比多行 if/else 更加紧凑。

练习 2:优惠券领取资格判断(嵌套分支与卫语句重构)

python
# 方式一:传统嵌套分支(单出口结构)
def can_claim_coupon(
    is_logged_in: bool, is_vip: bool, order_amount: float
) -> bool:
    """根据登录状态、会员身份与订单金额判断是否可以领取优惠券(传统嵌套分支)。

    参数:
        is_logged_in (bool): 用户是否已登录
        is_vip (bool): 用户是否为 VIP 会员
        order_amount (float): 订单金额(元)
    返回:
        bool: 是否可以领取优惠券
    """
    if is_logged_in:
        if is_vip or order_amount >= 100.0:
            eligible = True
        else:
            eligible = False
    else:
        eligible = False

    return eligible


# 方式二:卫语句结构(提前退出)
def can_claim_coupon_guard(
    is_logged_in: bool, is_vip: bool, order_amount: float
) -> bool:
    """根据登录状态、会员身份与订单金额判断是否可以领取优惠券(卫语句结构)。

    参数:
        is_logged_in (bool): 用户是否已登录
        is_vip (bool): 用户是否为 VIP 会员
        order_amount (float): 订单金额(元)
    返回:
        bool: 是否可以领取优惠券
    """
    # 卫语句 1:未登录直接返回 False
    if not is_logged_in:
        return False

    # 卫语句 2:满足任一领券条件即返回 True
    if is_vip or order_amount >= 100.0:
        return True

    # 默认返回:已登录但未达标
    return False


# 测试调用:核对两种实现的返回值一致性
test_cases = [
    (False, True, 120.0),   # 用例 1:未登录
    (True, False, 80.0),    # 用例 2:已登录,非 VIP 且金额不足
    (True, True, 80.0),     # 用例 3:已登录,VIP 会员
    (True, False, 120.0),   # 用例 4:已登录,金额达标
    (True, False, 100.0),   # 用例 5:边界情况,金额恰好满 100 元
]

for logged_in, vip, amount in test_cases:
    res_nested = can_claim_coupon(logged_in, vip, amount)
    res_guard = can_claim_coupon_guard(logged_in, vip, amount)
    print(
        f"输入: ({logged_in}, {vip}, {amount}) "
        f"-> 嵌套分支: {res_nested}, 卫语句: {res_guard}"
    )

输出:

text
输入: (False, True, 120.0) -> 嵌套分支: False, 卫语句: False
输入: (True, False, 80.0) -> 嵌套分支: False, 卫语句: False
输入: (True, True, 80.0) -> 嵌套分支: True, 卫语句: True
输入: (True, False, 120.0) -> 嵌套分支: True, 卫语句: True
输入: (True, False, 100.0) -> 嵌套分支: True, 卫语句: True

编程指引:

  1. 函数命名与返回值:用来判断“是否具备某种资格”的函数,通常以 can_、is_ 或 has_ 开头命名(如 can_claim_coupon),返回布尔值 True 或 False。这样在调用时语句很自然,例如 if can_claim_coupon(...):。
  2. 嵌套分支与单出口结构:方式一先在外层检查是否登录,再在内层检查是否满足领券条件。代码使用局部变量 eligible 保存判断结果,最后在末尾统一返回。这种写法的优点是出口唯一,逻辑层级清晰;缺点是嵌套层数较多,代码向右缩进较深。
  3. 卫语句的思考方式:方式二改变了思考角度,优先排除不符合条件的情况:
    • 提前排除未登录:第一行用 if not is_logged_in: return False 拦截未登录情况。只要未登录,函数立即结束;
    • 简化后续条件:程序能走到下一行,说明用户必然已经登录,因此不需要再用 else 缩进包裹,直接判断 is_vip or order_amount >= 100.0 即可;
    • 默认返回:如果前面的条件都不满足,说明是已登录但未达标的用户,最后返回 False。 通过提前返回,代码消除了多层缩进,阅读顺序自上而下,逻辑一目了然。

章节导航:上一章:第三章 常用内置函数与函数调用


补充说明(Tips)

正文中的编号标记对应以下专题说明。正文为保持主线清晰,对部分底层机制与边界条件作了适度简化;本节补充其严格定义与运行细节,供进阶学习时查阅,初学时可以忽略。

提示 1:条件表达式与真值测试

Python 的 if 语句基于**真值测试(Truth Value Testing)**决定分支执行路径:它直接检验条件表达式求值对象的“真值”,支持任意类型的对象参与条件判断。

在 Python 的对象模型中,任何对象都具有固有的真假属性。常见规则如下:

  • 真值为假(Falsy):空值 None、布尔值 False、各类数值零(如 0、0.0)、空序列与空集合(如空字符串 ""、空列表 []);
  • 真值为真(Truthy):除上述假值对象之外的绝大多数对象,包括所有非零数值和非空字符串。

因此,if "VIP10": 属于合法的 Python 语法。由于非空字符串 "VIP10" 的真值为真,条件判断直接成立并进入对应分支。在工程实践中,推荐优先使用比较表达式(如 order_amount > 0)或明确命名的布尔变量作为条件,使业务意图更直观清晰。

参考:Python 官方文档:真值测试。

提示 2:and、or 的返回值机制与真值区别

在许多编程语言中,逻辑运算符仅返回布尔值;但 Python 的 and 与 or 遵循 短路求值(Short-circuit Evaluation) 规则,直接返回决定运算结果的操作数本身,并不强制将其转换为布尔类型(仅 not 运算始终返回严格的 True 或 False):

  • x and y:若 x 的真值为假,整体结果已注定为假,立即返回 x;若 x 为真,整体结果由 y 决定,返回 y。例如 0 and 2 返回 0,1 and 2 返回整数 2;
  • x or y:若 x 的真值为真,整体结果已注定为真,立即返回 x;若 x 为假,整体结果由 y 决定,返回 y。例如 "apple" or "banana" 返回 "apple",False or "VIP10" 返回字符串 "VIP10"。

正文为了便于初学,所展示的逻辑真值表均以“操作数本身即为布尔值”为前提。而在 4.4 节的优惠码错误写法中,表达式为 coupon == "VIP9" or "VIP10":当左侧比较为 False 时,or 运算直接返回了右侧的操作数 "VIP10"。随后,if 语句对该字符串进行真值测试,由于非空字符串为真,条件判定意外通过。

编写逻辑条件时,需严格区分逻辑表达式返回的具体对象与该对象在条件判断中表现出的真假性。

参考:Python 官方文档:布尔运算。

提示 3:多分支条件的排列顺序与区间关系

if/elif/else 结构在运行时自上而下依次求值,具有单路匹配即终止的特性:只要命中首个成立的条件,执行对应分支后便会跳出整个分支结构,后续的所有条件均不再求值。

正文所强调的“由严到宽”准则,适用于条件范围存在相互包含关系的场景。正文案例采用单侧下界判断(score >= 90、score >= 80、score >= 60):

  • 在数轴集合上,[90, 100] \subset [80, 100] \subset [60, 100];
  • 若将较宽的条件 score >= 60 置于上方,高分(如 95 分)会在第一时间命中及格分支并立即退出,导致后续更严格的优秀分支永远无法被执行。因此,阈值必须按从严到宽(从大到小)严格排列。

相对地,如果将条件改写为两两互斥的闭合区间(如 score >= 90、80 <= score < 90、60 <= score < 80),任意具体成绩在数轴上至多只落入一个区间内,集合互不重叠。此时无论怎样打乱 elif 的先后顺序,最终的评级结果均完全一致。

单侧条件写法省略了区间上限的书写,代码更简练,但必须严格遵守排列顺序;互斥区间写法虽无顺序依赖,但表达相对繁琐。

参考:Python 官方文档:if 语句。

提示 4:运算符优先级与运行时求值顺序的区别

分析复合表达式时,需明确区分优先级与求值顺序这两个不同阶段的规则:

  • 优先级(Precedence):属于语法解析阶段,决定表达式中各操作数与运算符的结合方式(即语法树的构造);
  • 求值顺序(Evaluation Order):属于运行时执行阶段,决定各子表达式何时被实际计算,并受短路规则控制。

运算优先级决定操作数在语法树中的结合归属,而运行时的实际计算始终由求值顺序与短路规则主导。即便子表达式包含更高优先级的运算,只要逻辑运算符的左操作数已确定整体真值,解释器便直接跳过右侧子表达式。

以表达式 True or (1 / 0 > 0) 为例:

  1. 语法解析层面:圆括号与高优先级将 (1 / 0 > 0) 完整界定为 or 运算的右操作数;
  2. 运行时执行层面:Python 遵循从左至右的求值顺序。左操作数 True 算出后,依据 or 的短路规则,整体真值已然确定,右侧表达式被整体跳过;
  3. 运行结果:表达式求值为 True;由于除法子表达式未被执行,程序安全运行,不会触发除以零错误(ZeroDivisionError)。

综上,圆括号用于明确语法层面的分组与结合;运行时的执行流则始终遵循短路求值规则。

参考:Python 官方文档:求值顺序与运算符优先级。

提示 5:成绩区间的离散表述与代码的连续区间

正文 4.3.1 节表格中“80 分至 89 分”、“60 分至 79 分”的表述,采用了日常生活中成绩均为整数的离散约定(两整数之间不存在其他分值)。

然而在程序中,成绩变量通常允许为浮点数(如 89.5 分)。若将日常表述机械视为闭区间 [80, 89] 与 [90, 100],数轴上的 (89, 90) 之间就会出现判定空白。正文中编写的多分支代码,实质上是将连续数轴划分为无缝衔接的半开半闭区间:

  • 优秀:[90, 100](对应代码 score >= 90);
  • 良好:[80, 90)(对应代码 80 <= score < 90);
  • 及格:[60, 80)(对应代码 60 <= score < 80);
  • 不及格:[0, 60)(对应代码 score < 60)。

例如,当成绩为 89.5 分时,它落在 [80, 90) 区间内,代码会将其准确判定为“良好”。在工程开发中,将自然语言业务规则转化为分支条件时,应当警惕离散描述造成的边界缝隙,明确区间端点的开闭属性。