跳到主要内容
Python 程序设计 · 在线教材适用于 2026—2027 学年第一学期

第 4 章 · M1 活动报名基础版

循环与批量报名

第 3 章的判定器能给一名申请者明确结果;面对多份申请,还需要让同一套规则重复运行,并准确记录人数与实收金额。本章把循环、分支和状态更新合成你的第一个个人项目里程碑。

预计阅读
65 分钟
上机实践
90 分钟
前置知识
第 1—3 章
本章产出
个人 M1 报名基础版

学完本章,你应该能够

  • 说明一轮循环、循环体、结束条件与状态更新分别是什么。
  • 用 for 和 range() 按计划次数处理申请,避免漏掉最后一份。
  • 用 while 表达条件控制的重复,并指出循环如何终止。
  • 区分计数器与累加器,在正确分支更新报名人数和实收金额。
  • 解释 break 与 continue 的不同,并用跟踪表核对多轮状态。
  • 独立完成 M1 程序、三组测试和一次限时修改。

4.1 从处理一人到处理多人

假设迎新活动收到 30 份申请。把第 3 章的代码复制 30 遍,看似可以工作,却会带来三个问题:程序冗长;规则一旦变化,容易漏改某一份;申请数变动时又得重新复制。循环(loop)让一段缩进代码按规则重复执行。循环体完整执行一次,叫作一轮或一次迭代(iteration)。

写循环前,先问四件事:重复什么任务?预定多少轮或何时结束?每轮哪些数据会变化?什么变化保证最终停下?本项目的答案是“每份申请输入、判定一次;最多处理本次申请人数;成功时增加已报名人数;处理完计划轮数或名额已满时结束”。

阶段M1 程序中的工作
循环前读取活动初始数据;把成功数、失败数和实收金额设为 0
每轮先查名额,再读取一名申请者,沿用身份与费用规则,按结果更新状态
循环后计算剩余名额,只输出一次最终汇总
本章的知识边界

我们只统计成功、失败人数和金额,不保存每个申请者的姓名。名单保存要等第 6 章学习列表;本章也不使用文件或异常处理。

4.2 for:按顺序重复循环体

当计划处理多少份申请已经知道时,for 很合适。它每轮从 range() 提供的整数中取一个值,交给循环变量,再执行一次缩进的循环体。

first_for.py
for sequence in range(1, 4):
    print(f"第{sequence}份申请")
    print("正在检查")
print("本批次结束")
运行结果
第1份申请
正在检查
第2份申请
正在检查
第3份申请
正在检查
本批次结束

先预测“本批次结束”会打印几次:它与 for 对齐,不属于缩进循环体,所以只打印一次。若把最终汇总缩进进去,就会每轮都输出一份尚未完成的报告。循环变量 sequence 在三轮中依次取 1、2、3。

次数与结果不要混淆

计划循环三轮,只说明最多检查三份申请;不代表三人都会报名成功。失败申请不占名额,满额时还可能提前结束。

4.3 range():包含开始,不包含停止

range(stop) 从 0 开始,到 stop 之前结束;range(start, stop) 从 start 开始,仍不包含 stop。它提供的是可按顺序取出的整数,不要求先创建列表。

range_boundaries.py
for number in range(3):
    print(number)
print("---")
for sequence in range(1, 4):
    print(sequence)
运行结果
0
1
2
---
1
2
3

若本次有 application_count 份申请,编号从 1 到它本身,停止值就要写成 application_count + 1。多写的 1 不是多处理一人,而是补偿“不包含停止值”。

application_numbers.py
application_count = 3
for sequence in range(1, application_count + 1):
    print(f"申请 {sequence}")
运行结果
申请 1
申请 2
申请 3

若误写 range(1, application_count),第 3 份会漏掉。这种少一轮或多一轮的现象叫“差一错误”。当申请人数为 0 时,range(1, 1) 没有可取的数,循环体一次也不执行;这是后面必须测试的边界。

4.4 计数器与累加器:把多轮结果留下来

状态(state)是某一时刻影响后续判定和最终报告的数据。本项目至少有四项状态:当前已报名人数、成功人数、失败人数、实收金额。计数器记录事件发生的次数,通常每次加 1;累加器汇总每轮产生的数值,增加的量不一定相同。

counter_and_accumulator.py
success_count = 0
total_collected = 0.0
success_count = success_count + 1
total_collected = total_collected + 20.0
success_count = success_count + 1
total_collected = total_collected + 25.0
print(success_count)
print(f"{total_collected:.2f}")
运行结果
2
45.00

两个成功事件让计数器从 0 变成 2;两笔不同金额让累加器变成 45.00。初始化必须在循环前做一次;若把 success_count = 0 放进循环体,下一轮会抹掉上一轮的记录。

本轮结果registeredsuccess_countfailure_counttotal_collected
身份失败不变不变加 1不变
费用失败不变不变加 1不变
报名成功加 1加 1不变加本轮已付金额

注意“已报名人数”包含运行前已经报上的人;“成功人数”只统计这一批新成功的人。两者不应被当成同一个数。

4.5 while:由条件决定下一轮

当次数未知、但结束条件明确时,可以使用 while。每次准备进入循环体之前,解释器都会重新计算条件。设计时要看清初始化、条件、循环体和状态更新四个位置。

while_count.py
round_number = 1
while round_number <= 3:
    print(f"第{round_number}轮")
    round_number = round_number + 1
print("结束")
运行结果
第1轮
第2轮
第3轮
结束

第 3 轮末尾,round_number 变成 4;下一次检查 4 <= 3 为假,循环才停。若忘记更新,条件会持续为真,可能形成无限循环;不要在在线运行器中直接尝试故意不更新的版本。

不知道人数时,用约定的结束信号

下面把 q 约定为“停止输入”的哨兵值。第一次检查条件前必须先读一次;每轮末尾还要再次读取,否则条件永远检查旧值。

sentinel_input.py
student_name = input("姓名(q结束):")
while student_name != "q":
    print(f"已接收:{student_name}")
    student_name = input("姓名(q结束):")
print("受理结束")
运行结果
姓名(q结束):小林
已接收:小林
姓名(q结束):小周
已接收:小周
姓名(q结束):q
受理结束

这只是理解 while 的独立小例子;M1 基础版仍用已知申请人数的 for。真实姓名若可能就是 q,就不能随意把它当结束信号;本周将其作为约定好的练习输入。

4.6 break 与 continue:停下还是跳过

两者都改变正常的逐行执行,但范围不同:break 立即离开整个循环;continue 只跳过本轮剩余语句,开始下一轮。它们通常与 if 配合使用。

break_demo.py
for sequence in range(1, 5):
    if sequence == 3:
        print("停止受理")
        break
    print(f"处理第{sequence}份")
print("输出汇总")
运行结果
处理第1份
处理第2份
停止受理
输出汇总
continue_demo.py
for sequence in range(1, 4):
    if sequence == 2:
        print("第2份无效,跳过本轮")
        continue
    print(f"处理第{sequence}份")
print("输出汇总")
运行结果
处理第1份
第2份无效,跳过本轮
处理第3份
输出汇总

M1 中,若本轮开始就已满额,继续询问姓名和金额没有意义,应在读取申请前 break。continue 可以用于进阶版跳过格式无效的一份申请,但要先记录无效输入;它不是“悄悄不统计失败”的借口。

放在 continue 后面的更新可能被跳过

如果在 while 中,推进结束条件的语句写在 continue 后面,它可能永远得不到执行,造成死循环。因此先明确每轮哪些状态必须更新,再决定是否跳过。

4.7 用循环跟踪表检查状态

只看最后报告,难以发现哪一轮把状态更新错了。循环跟踪表把“进入本轮前—本轮结果—更新后”排在同一行。下面设容量为 3、初始已报名 1 人、活动费 20 元,计划处理 4 份申请。

计划轮次处理前报名本轮发生什么处理后报名成功 / 失败实收
11A:本校、付足,成功21 / 020
22B:非本校,失败21 / 120
32C:本校、付足,成功32 / 140
43先发现满额,break32 / 140

计划有四轮,真正读取了三个人;第四轮还没问姓名就停了。结果是成功 2、失败 1、剩余 0、实收 40。失败的 B 不改变已报名人数,也不增加实收。这个表就是课堂课件“状态跟踪”实验的书面版本。

4.8 把循环和第 3 章规则合成 M1

下面的完整基础版,先读取活动的四项初始数据,再初始化三个汇总变量。每轮先看名额;未满才读取一名申请者,依次检查身份与费用。只在成功路径更新 registered、成功数和实收金额。请保存为 batch_registration.py,从头运行。

batch_registration.py
# 第4章 M1 基础版:批量活动报名与汇总
capacity = int(input("活动容量:"))
registered = int(input("当前已报名人数:"))
activity_fee = float(input("每人活动费:"))
application_count = int(input("本次申请人数:"))

success_count = 0
failure_count = 0
total_collected = 0.0

for sequence in range(1, application_count + 1):
    if registered >= capacity:
        print("名额已满,停止受理")
        break

    print(f"\n第{sequence}份申请")
    student_name = input("姓名:")
    campus_status = input("是否本校学生(y/n):")
    paid_amount = float(input("已支付金额:"))

    if campus_status != "y":
        failure_count = failure_count + 1
        print(f"{student_name}报名失败:身份不符合")
    elif paid_amount < activity_fee:
        failure_count = failure_count + 1
        print(f"{student_name}报名失败:费用不足")
    else:
        registered = registered + 1
        success_count = success_count + 1
        total_collected = total_collected + paid_amount
        print(f"{student_name}报名成功")

remaining_places = capacity - registered

print("\n===== 报名汇总 =====")
print(f"成功人数:{success_count}")
print(f"失败人数:{failure_count}")
print(f"剩余名额:{remaining_places}")
print(f"实收金额:{total_collected:.2f} 元")
运行结果
活动容量:5
当前已报名人数:2
每人活动费:20
本次申请人数:3

第1份申请
姓名:A
是否本校学生(y/n):y
已支付金额:20
A报名成功

第2份申请
姓名:B
是否本校学生(y/n):n
已支付金额:20
B报名失败:身份不符合

第3份申请
姓名:C
是否本校学生(y/n):y
已支付金额:10
C报名失败:费用不足

===== 报名汇总 =====
成功人数:1
失败人数:2
剩余名额:2
实收金额:20.00 元

运行结果中的数字、姓名和 y/n 是示例键盘输入,不属于源代码。点击“在线运行”会先复制代码并打开新标签页;你仍需粘贴代码,再按提示逐项提供输入。下载文件与上方完整示例相同。

  1. 1
    初始数据

    容量、当前已报名、每人费用、本次申请人数,分别转换为合适的数值类型。

  2. 2
    初始化状态

    成功数、失败数和实收金额从零开始,只在循环前执行一次。

  3. 3
    先满额,后输入

    第 3 章的满额规则仍是第一优先级;满足时直接结束,不向后面的申请者索取数据。

  4. 4
    本轮判定与更新

    失败只增加失败数;成功占一个名额,并计入已付金额。

  5. 5
    循环后汇总

    剩余名额由最终状态计算,最终报告只出现一次。

与第 3 章的衔接

“满额 → 身份 → 费用 → 成功”的业务优先级没有变化;只是满额现在可以停止整批受理,身份与费用失败后仍继续下一份。不要为了循环而改变原有业务规则。

4.9 三组必测数据与真实排错

每组先写下预期,再独立运行一次完整程序,把实际值填进个人测试表。不能只用“第一人成功”证明批量处理正确;M1 必须覆盖混合路径、中途满额和零次申请。

测试输入预期汇总检验点
A · 混合容量 5、初始 2、费 20、申请 3;A/y/20、B/n/20、C/y/10成功 1、失败 2、剩余 2、实收 20.00失败不占名额、不计金额
B · 中途满额容量 3、初始 2、费 20、申请 3;第一人 y/20成功 1、失败 0、剩余 0、实收 20.00第二轮开始就 break,不再读取姓名
C · 零次申请容量 5、初始 2、费 20、申请 0成功 0、失败 0、剩余 3、实收 0.00循环体零次执行,汇总仍出现一次

结果不对时,从第一处状态分叉查起

如果 A 的成功数是 1,但实收变成 50.00,检查是否在失败路径也累加了金额;若申请 3 人只出现两轮,检查 range() 的停止值;若 C 没有最终报告,检查汇总是否误缩进在循环体中。对于 while 不停的情况,检查参与条件的变量是否在每条继续路径上都能更新。

排查顺序范围边界→缩进→初始化→分支更新→满额与 break

本版假定人数和费用输入有效,float() 遇到非数字文本仍会报错;它也不检查负数、超容量等异常初始数据。把这些情况记录为当前限制,不要把“程序没有处理”写成“测试通过”。

4.10 练习与 M1 个人验收

先预测,再看解释

  1. 读范围

    range(2, 6) 会给出哪些数?range(1, 3) 能否处理第 3 份申请?

    查看答案

    依次是 2、3、4、5;range(1, 3) 只有 1、2,会漏掉第 3 份。

  2. 读状态

    容量 3、初始报名 1,第一人成功、第二人身份失败后,registered 与失败数各是多少?

    查看答案

    registered 从 1 变成 2,第二人失败不占名额;失败数为 1。

  3. 选控制语句

    名额已满要停止整批受理;某份申请格式无效,但还要处理下一份。两处分别用什么?

    查看答案

    前者用 break,后者可用 continue;跳过前要按自己设计的规则记录这份无效输入。

  4. 找终止条件

    while round_number <= 3 中若一直不改变 round_number,会发生什么?

    查看答案

    条件可能始终为真而无限循环。每轮应让控制变量朝不满足条件的方向更新,并检查跳过路径不会遗漏更新。

第 4 周 · M1 个人里程碑

完成自己的活动报名基础版

每位学生独立维护、提交、测试并解释自己的 ClubHub 版本。示例用于理解结构;请自行选定活动情境与测试数据,并完成现场限时修改。临时同伴互测只提供反馈,不替代个人证据。

最低验收要求

  • 提交 w04_学号_batch_registration.py,文件开头写姓名、学号和用途注释。
  • 接收 4 项活动初始数据,循环读取每名申请者的姓名、身份和已付金额。
  • 沿用第 3 章规则;满额时先检查再输入,成功与失败只更新各自应更新的状态。
  • 只输出一次成功数、失败数、剩余名额和两位小数的实收金额。
  • 提交三组“输入—预期—实际—结论”测试记录,能用跟踪表解释至少三轮状态变化。
  • 完成个人限时修改:增加 cancelled_count;姓名输入 q 时加 1 并 break,不再询问身份和金额,最终汇总显示提前结束次数。
  • 保留限时修改的运行证据,并附 80—150 字错误复盘。
下载基础版完整示例

进阶可为不是 y/n 的身份输入单独计数,并用 continue 跳过本轮;挑战可用 while 与 q 重写受理过程。这些扩展应先写结束条件和测试预期,不属于基础版三组必测的替代品。

章末检查

  • 我知道 range() 不包含停止值,能解释 + 1 的原因。
  • 我把汇总变量初始化在循环前,把最终报告放在循环后。
  • 我能解释失败申请为何不改变已报名人数与实收金额。
  • 我能区分计划轮数、实际读取人数和成功人数。
  • 我用三组测试和一次个人修改证明 M1 可运行、可解释。

延伸阅读:廖雪峰《Python教程》在线 PDF中的“循环”,以及菜鸟教程 Python 3 循环语句。本章用 ClubHub 的报名与汇总问题重新组织例子,以课程主数据的知识边界为准。