第 3 章 · 让程序学会作决定
分支与报名判定
第二章的程序会接收和计算数据,却不会根据规则选择结果。本章把“名额、身份、费用”的业务要求翻译成布尔条件与分支路径,让每一次报名都有唯一、可解释的判定。
- 预计阅读
- 60 分钟
- 上机实践
- 90 分钟
- 前置知识
- 第 1—2 章
- 本章产出
- 个人报名判定器
学完本章,你应该能够
- 读懂
True、False和六个比较运算符,区分赋值=与比较==。 - 用
if、elif、else和正确缩进表达互斥规则。 - 用
and、or、not组合清楚的条件。 - 先画决策表,再写报名判定程序,并解释条件顺序为何重要。
- 用四条路径和边界值核验结果,而不只检查一次“运行成功”。
3.1 从“会计算”走向“会判断”
第 2 章的预算计算器不论输入哪项活动,都会沿同一条路线完成计算。ClubHub 的报名流程不同:名额已满时要拒绝;有名额但不是本校学生,也要拒绝;前两项通过而费用未付足,仍要拒绝。只有三项都通过,才能成功报名。
把问题写成“输入—处理—输出”(IPO),可以看出本章新增的步骤是选择路径,而不是增加一条算式。
| 阶段 | 报名判定器要做什么 |
|---|---|
| 输入 | 姓名、活动总名额、已报名人数、校内身份、应付金额、已付金额 |
| 处理 | 按“名额 → 身份 → 费用”逐项判断;成功时人数加 1,再算剩余名额 |
| 输出 | 姓名和唯一的主要结果:三种失败原因之一,或成功及剩余名额 |
如果一位申请者既遇到满额又未付足费用,本章约定先报告“名额已满”。这不是 Python 自动决定的;它是我们先写进规则、再用分支顺序实现的业务优先级。
3.2 布尔值与比较表达式
“当前人数是否已经达到容量?”是一个能回答真或假的问题。Python 用布尔值 True 和 False 表示这两种结果;它们首字母大写,不加引号。写成 "True" 就是普通字符串,不是布尔值。
capacity = 30
registered = 29
print(registered < capacity)
print(registered >= capacity)
print(type(registered < capacity))True False <class 'bool'>
第一项提问“还有没有名额”,第二项提问“是否满额”。同一组数据让前者为真、后者为假。type() 显示比较结果属于 bool 类型。
| 运算符 | 意思 | 报名情境 |
|---|---|---|
== | 等于 | campus_status == "y" |
!= | 不等于 | campus_status != "y" |
> | 大于 | registered > capacity |
>= | 大于等于 | registered >= capacity |
< | 小于 | paid_amount < activity_fee |
<= | 小于等于 | registered <= capacity |
一个等号做动作,两个等号问问题
capacity = 30
print(capacity == 30)
capacity = 31
print(capacity == 30)True False
capacity = 30 把右侧的值关联给名称;capacity == 30 不改变变量,只产生布尔结果。在 if 条件中误写 if campus_status = "y": 会产生语法错误。
3.3 用边界选对比较符
容量为 30 时,29 人尚有空位,30 人恰好满额,31 人虽然提示数据可能有问题,也不能继续放行。业务语言里的“已经满额”因此要写作 registered >= capacity,不能只写 ==。
capacity = 30
registered = 29
print(registered >= capacity)
registered = 30
print(registered >= capacity)
registered = 31
print(registered >= capacity)False True True
| 业务说法 | 正确表达式 | 边界上的结果 |
|---|---|---|
| 仍有名额 | registered < capacity | 30 / 30 为假 |
| 已经满额 | registered >= capacity | 30 / 30 为真 |
| 至少支付 20 元 | paid_amount >= 20 | 支付 20 元为真 |
| 费用不足 | paid_amount < activity_fee | 19.99 / 20 为真 |
不要凭感觉选符号。先把规则切换的点写出来,再分别测试边界下方、恰好边界、边界上方。课堂课件中的 29、30、31 和 19.99、20、20.01 正是这样的样本。
3.4 if 与缩进:真时才执行
if 后跟一个条件,末尾要有英文冒号。条件为 True 时,执行下方缩进的代码块;为 False 时跳过它。缩进是 Python 识别代码块的语法,不是美化排版。
registered = 30
capacity = 30
if registered >= capacity:
print("报名失败:活动名额已满")
print("判定结束")报名失败:活动名额已满 判定结束
把 registered 改成 29 再运行,只会输出“判定结束”。它与 if 对齐,在分支之外,因此两种情况都会执行。若误把最后一行也缩进,就会产生“只有满额时才显示判定结束”的逻辑错误;若 if 后一行完全不缩进,则会出现 IndentationError。
本课程统一在同一层级使用 4 个空格缩进。冒号、括号与引号都使用英文符号。编辑器通常会在按回车后自动缩进;仍要自己检查对齐层级。
3.5 if-else:两条互斥路径
如果希望条件为真与为假时分别做事,就在 if 后加 else。同一组 if-else 只走其中一条路径,不会两条都走。
registered = 29
capacity = 30
if registered >= capacity:
print("报名失败:活动名额已满")
else:
print("还有名额,继续检查身份和费用")还有名额,继续检查身份和费用
这里的 else 只表示“未满额”,不代表已经可以报名成功。如果此时就输出成功,将跳过身份与费用规则。正确做法是让后续条件继续检查,全部通过后才更新人数。
3.6 elif:顺序就是优先级
报名有三种不同的失败原因,一组 if-elif-else 可以从上到下逐项排除。命中第一条为真的路径后,该组后面的 elif 与 else 都不再执行;因此本章能保证只给出一个主要结果。
registered = 30
capacity = 30
campus_status = "n"
activity_fee = 20.0
paid_amount = 0.0
if registered >= capacity:
print("名额已满")
elif campus_status != "y":
print("身份不符")
elif paid_amount < activity_fee:
print("费用不足")
else:
print("报名成功")名额已满
这组输入同时违反三条规则,却只显示“名额已满”,因为它是约定的首要原因。若把“有名额就成功”写在第一条,29 人、身份为 n 的申请者会被错误放行。三个彼此独立的 if 也不适合这里:它们可能连续输出多条失败信息。
先排除失败:满额 → 非本校 → 费用不足 → 其余情况才成功else 不带条件,表示前面所有失败条件都没有命中。它是本项目成功路径最清楚的位置。
3.7 用 and、or、not 组合条件
一个判断有时由多个问题组成。and 要求两边都为真;or 只要至少一边为真;not 把真假反过来。先把每个判断存入有业务含义的变量,再组合,通常比一整行长条件更容易读。
capacity = 30
registered = 29
campus_status = "y"
activity_fee = 20.0
paid_amount = 20.0
has_place = registered < capacity
is_student = campus_status == "y"
fee_ok = paid_amount >= activity_fee
can_register = has_place and is_student and fee_ok
print(has_place, is_student, fee_ok)
print(can_register)
print(not has_place)True True True True False
这里三项均为真,can_register 才为真;not has_place 表示“没有空位”,所以为假。若允许本校学生或教师,身份规则可以写成下面这样;这是进阶变体,不是本章基础项目的规则。
campus_status = "teacher"
identity_ok = campus_status == "y" or campus_status == "teacher"
print(identity_ok)True
在这些布尔条件中,比较先于 not,然后是 and、or。混合多种运算时,用括号或中间变量明确意图。Python 的 and 和 or 还会短路求值:当前一部分已足以确定结果时,后一部分可能不再计算;本章先以条件清晰、可解释为目标。
can_register 很适合回答“能不能”;报名系统还要回答“为什么不能”,所以完整项目仍使用按优先级排列的多分支。
3.8 编码前先写规则决策表
决策表把输入情形、首个命中的条件和预期结果放在一起。先写预期、再运行程序,才能真正检验规则;如果只看程序跑出了什么,再把它抄作“预期”,错误也会被当成正确。
| 用例 | 已报名 / 容量 | 身份 | 已付 / 应付 | 首条命中路径 | 预期 |
|---|---|---|---|---|---|
| A | 30 / 30 | y | 20 / 20 | 满额 | 名额已满 |
| B | 29 / 30 | n | 20 / 20 | 身份 | 仅限本校学生 |
| C | 29 / 30 | y | 19.99 / 20 | 费用 | 活动费用未付足 |
| D | 29 / 30 | y | 20 / 20 | 成功 | 报名成功,剩余 0 |
A 验证恰好满额,C 与 D 夹住“付足 20 元”的边界,D 同时验证成功后的人数更新。进一步让已报名人数为 31,应仍进入满额路径。若多个失败条件同时成立,按表中规则仍只显示优先级最高的原因。
3.9 完成活动报名判定器
现在把第 2 章学过的输入与类型转换,和本章的比较、分支组合起来。保存为 registration_judge.py,一次运行只处理一名申请者。请先预测用例 D 的结果,再复制代码运行。
# 第3章:一次处理一名申请者
# 规则优先级:名额 → 身份 → 费用 → 成功
student_name = input("请输入姓名:")
capacity = int(input("请输入活动总名额:"))
registered = int(input("请输入当前已报名人数:"))
campus_status = input("是否为本校学生(y/n):")
activity_fee = float(input("请输入应付活动费:"))
paid_amount = float(input("请输入已支付金额:"))
if registered >= capacity:
result = "报名失败:活动名额已满"
elif campus_status != "y":
result = "报名失败:本活动仅限本校学生"
elif paid_amount < activity_fee:
result = "报名失败:活动费用未付足"
else:
registered = registered + 1
remaining_places = capacity - registered
result = f"报名成功,剩余名额:{remaining_places}"
print()
print(f"学生:{student_name}")
print(result)请输入姓名:李明 请输入活动总名额:30 请输入当前已报名人数:29 是否为本校学生(y/n):y 请输入应付活动费:20 请输入已支付金额:20 学生:李明 报名成功,剩余名额:0
上方运行结果中的“李明、30、29、y、20、20”是示例键盘输入,不是源代码。在线运行时,在运行器中按提示逐项输入;复制按钮只复制上方的程序。
- 1输入六项数据
人数用
int(),金额用float()。campus_status暂时约定为小写y或n。 - 2先排除失败
依次检查满额、身份和费用。每条失败路径给出不同的解释。
- 3成功后更新状态
只有走进
else才让registered加 1;随后用新人数计算剩余名额。 - 4统一输出
最后只打印一次姓名和结果,避免多个分支重复打印或同时报出几个原因。
它一次只处理一人;大写 Y 不等于小写 y;非数字输入可能使转换失败;人数或金额的异常值尚未单独校验。这些不是“已经支持”的功能,也不要用循环、异常处理或 match-case 偷换本章练习目标。
3.10 用四条路径和边界值排错
把上一节的程序从头运行四次,逐项输入决策表的 A—D。姓名可以自选,但每次都要在运行前记录“预期”,运行后记录“实际”。四次都符合,才说明四条核心路径得到覆盖。
| 测试 | 重点观察 | 如果结果不对,先检查 |
|---|---|---|
| A · 满额 | 30 / 30 不成功;31 / 30 也不成功 | >= 是否误写成 > 或 == |
| B · 身份 | 有名额但 n 被拒绝 | 是否过早把“有名额”当作成功 |
| C · 费用 | 19.99 / 20 被拒绝 | < 方向与金额变量是否写反 |
| D · 成功 | 20 / 20 通过,29 加 1 后剩余 0 | 成功更新是否在 else 内、计算顺序是否正确 |
错误不止一种
漏英文冒号、if 后没有缩进,属于语法或缩进错误,程序不能正常开始;把最后的输出误缩进进分支,程序可能能运行,但路径不符合预期;把“至少支付”写成 >,则会在恰好付足时出现边界错误。语法报错时读报错行附近;能运行但结果不对时,拿决策表追踪首个命中的条件。
进阶观察:输入 Y 时,本版会进入“仅限本校学生”路径。它揭示了大小写处理尚未实现;第 5 章学习字符串清洗后再改。输入汉字金额会触发转换错误,异常处理留到后续章节。
3.11 练习与个人任务
先预测,再看答案
- 读布尔结果
capacity = 30、registered = 30时,registered < capacity与registered >= capacity各是什么?查看答案
依次为
False与True。恰好 30 人已无空位。 - 判断路径
29 / 30、身份
n、已付 0 / 应付 20,应该输出哪一个原因?为什么不是两个?查看答案
输出“本活动仅限本校学生”。名额尚有空位,第二条身份规则首先命中;同一组分支不会继续执行费用路径。
- 修正比较符
“支付金额至少 20 元才能通过”应使用
>还是>=?测试哪三个金额?查看答案
使用
>=;可测 19.99、20、20.01,分别位于边界下方、边界点和边界上方。 - 追踪状态
29 / 30 的申请者成功时,为什么剩余名额是 0 而不是 1?
查看答案
成功后先令
registered = registered + 1,新人数为 30,再计算30 - 30 = 0。
独立完成活动报名判定器
每位学生独立编写、测试并解释自己的程序。同伴可以临时互测、提供反馈,但源文件、决策表、测试记录和讲解均由本人完成与提交。
最低要求
- 程序接收姓名、容量、已报名人数、校内身份、应付与已付金额六项输入。
- 按“满额 → 身份 → 费用 → 成功”输出一个明确结果;成功时更新人数并显示剩余名额。
- 提交规则决策表和四条路径的“输入—预期—实际—结论”,另测 31 / 30 的边界。
- 提交一张成功路径、一张失败路径的截图,附 50—100 字错误复盘。
- 源文件命名为
w03_学号_registration_judge.py;课堂展示时能解释某组输入为何走到该分支。
基础四路径通过后,可任选进阶:允许教师身份,或新增 registered > capacity 的“数据异常”提示。先扩展规则表和测试,再修改代码;这些不是基础任务的必做项。
章末检查
- 我能解释
=、==和!=的不同作用。 - 我知道冒号和缩进决定哪个语句属于哪个分支。
- 我能说明为什么报名项目把成功留在最后的
else。 - 我在运行之前写过四条路径的预期,并测过等号附近的边界。
延伸阅读:廖雪峰《Python教程》在线 PDF中的“数据类型和变量”“条件判断”,以及菜鸟教程 Python 3 条件控制。本章按 ClubHub 报名规则重组示例,并以课程主数据的知识边界为准。