第 6 章 · 从计数走向具体成员
列表与报名名单管理
前几章能判断报名是否成功,却没有留下“到底是谁报名”的证据。本章用一个有顺序、可修改的列表记录姓名:录入时拒绝空名和重复,取消前确认成员存在,查询不改变数据,统计与排序展示都从同一份名单产生。
- 预计阅读
- 60 分钟
- 上机实践
- 90 分钟
- 前置知识
- 第 1—5 章
- 本章产出
- 个人报名名单管理器
学完本章,你应该能够
- 创建、访问并修改列表元素,说明索引从 0 开始以及空列表的边界。
- 正确使用
append()、remove()与in,拒绝空姓名和重复姓名。 - 使用
for遍历名单,区分元素值、列表索引和展示编号。 - 解释原地修改与产生新列表的区别,用
sorted()展示而不篡改报名顺序。 - 独立完成录入、取消、查询、统计和八组状态测试。
6.1 为什么人数不等于名单
假设屏幕只显示“报名 3 人”。它不能回答陈晨是否已报名,也不能判断下一次输入是否重复。把姓名分别写进 name1、name2、name3 也不灵活:人数变化就要改变量数量和处理代码。列表把同类数据放在一个容器中,并保留添加的先后顺序。
student_name = "陈晨"
student_name = "王宁"
members = ["陈晨", "王宁"]
print(student_name)
print(members)
print(len(members))王宁 ['陈晨', '王宁'] 2
第二次赋值使单个变量只剩王宁;列表仍保留两位成员。len(members) 根据实际名单计算人数,不必另设一个可能与名单脱节的计数器。本章暂以“清洗后的姓名”识别成员;真实场景有同名者,这一限制会在第 7 章用学号解决。
6.2 列表、索引与修改
用方括号创建列表,逗号隔开元素。第一个元素的索引是 0,最后一个也可用 -1 表示。对姓名列表来说,一个元素是完整姓名,不是一个汉字。列表可修改:给某个有效索引赋新值,长度不会变化;这与第 5 章不能原地改字符的字符串不同。
members = ["陈晨", "王宁"]
print(members[0], members[-1])
members[1] = "李悦"
print(members)
print(len(members))陈晨 王宁 ['陈晨', '李悦'] 2
这里的修改是“把第二个格子的值换掉”,不是新增一位;新的名单仍有两人。members[2] 在这个例子里会抛出 IndexError,因为有效的正索引只有 0 和 1。不要把展示给人的“第 1 位”误当作索引 1。
6.3 空列表也是合法状态
程序从 members = [] 开始。此时长度为 0,成员查询会得到 False,遍历会执行零次;但访问 members[0] 会越界。先判断是否有元素,再访问首位。
members = []
print(len(members))
print("陈晨" in members)
if len(members) > 0:
print(members[0])
else:
print("暂无成员")0 False 暂无成员
如果让空名单预先含有 "暂无成员",len() 就会把提示语当作一个报名者。空状态要用空列表表示,提示语只负责输出。
6.4 新增、删除与去重:先检查,再改变
append(value) 在列表末尾添加一个元素;remove(value) 按值删除第一个匹配元素。两者都会修改原列表,返回值都不是新名单。删除不存在的值会抛出 ValueError,所以先用 in 判断。
members = ["陈晨", "王宁"]
name = " 李悦 ".strip()
if name != "" and name not in members:
members.append(name)
print(members)
if "陈晨" in members:
members.remove("陈晨")
print(members)['陈晨', '王宁', '李悦'] ['王宁', '李悦']
清洗要发生在去重之前:" 李悦 " 和 "李悦" 应比较清洗后的姓名。判断优先级是“结束哨兵 → 空姓名 → 重复姓名 → 新增”;取消则是“空输入可跳过 → 存在才删除”。若重复输入被拒绝,名单和人数都应保持不变。
不要写 members = members.append(name) 或 members = members.remove(name)。方法已修改原列表,返回的 None 若被赋给 members,后续就不能再按列表使用了。第 6 周基础版在录入时拒绝重复姓名,因此 remove() 只会遇到一个匹配者。
6.5 遍历每个人,编号从 1 显示
如果只需要姓名,用 for name in members;如果还需要在屏幕上编号,可用 range(len(members)) 得到索引 0、1、2,再显示 index + 1。这里的编号只是界面文字,不改变列表内部索引。
members = ["陈晨", "王宁", "李悦"]
for name in members:
print("成员:", name)
for index in range(len(members)):
print(f"{index + 1}. {members[index]}")成员: 陈晨 成员: 王宁 成员: 李悦 1. 陈晨 2. 王宁 3. 李悦
此处是顺序展示。若取消了陈晨,后面成员的位置会前移,新的第 1 位就是王宁;不应继续沿用旧编号。
6.6 排序只用于展示,报名顺序仍要保留
sorted(members) 返回按默认规则排好的新列表,不会改动原名单;members.sort() 则原地重排并返回 None。本项目要保留报名先后,所以把排序结果存入另一个变量,只用来输出。
members = ["王宁", "李悦", "陈晨"]
display_members = sorted(members)
print("展示:", display_members)
print("原顺序:", members)
print("原地排序返回:", members.sort())
print("原地排序后:", members)展示: ['李悦', '王宁', '陈晨'] 原顺序: ['王宁', '李悦', '陈晨'] 原地排序返回: None 原地排序后: ['李悦', '王宁', '陈晨']
这段代码最后两行是对照实验,基础程序不调用 members.sort()。默认字符串排序依据字符编码,不等同于中文姓名的拼音顺序;实际顺序要以运行结果为准,不把它宣称为“按姓名拼音排序”。
6.7 完成列表版报名名单管理器
基础程序分四段:从空列表反复录入,q 或 Q 只结束录入;之后各做一次取消和查询;最后从名单本身统计人数并展示原顺序与排序结果。这里还不是第 8 周的完整菜单,所以取消、查询不会反复出现。
# 第6章:列表版报名名单管理器
# 教学简化:姓名暂作唯一标识;第7章再用学号区分同名学生。
members = []
while True:
student_name = input("新增姓名(q结束录入):").strip()
if student_name.lower() == "q":
break
if student_name == "":
print("新增失败:姓名不能为空")
elif student_name in members:
print("新增失败:姓名已存在")
else:
members.append(student_name)
print(f"新增成功:{student_name}")
cancel_name = input("取消姓名(直接回车跳过):").strip()
if cancel_name == "":
print("跳过取消")
elif cancel_name in members:
members.remove(cancel_name)
print(f"取消成功:{cancel_name}")
else:
print("取消失败:姓名不存在")
query_name = input("查询姓名(直接回车跳过):").strip()
if query_name == "":
print("跳过查询")
elif query_name in members:
print(f"查询结果:{query_name}已报名")
else:
print(f"查询结果:{query_name}未报名")
print(f"当前人数:{len(members)}")
print(f"原报名顺序:{members}")
print("排序展示:")
if len(members) == 0:
print("暂无成员")
else:
display_members = sorted(members)
for index in range(len(display_members)):
print(f"{index + 1}. {display_members[index]}")
print(f"展示后原名单:{members}")示例输入依次为“王宁、陈晨、李悦、q、陈晨、李悦”。应得到:陈晨取消成功,李悦已报名,当前人数 2;原顺序为王宁、李悦,排序展示为李悦、王宁,展示后原名单不变。运行需要按提示依次输入;“在线运行”会复制当前代码并打开指定的 W3Cschool 新标签页,需自行粘贴和输入数据。
- 1用一个真实名单承载状态
姓名只存在
members,人数由len(members)即时计算。 - 2失败操作不改变状态
空姓名、重复姓名、不存在的取消对象,只报告原因,不动列表。
- 3查询只读
查询前后人数和顺序应完全一样;排序展示也不改原名单。
6.8 八组状态测试与错误诊所
测试不要只看最后人数,还要检查具体姓名和顺序。表中箭头表示先后输入;“空”表示在取消或查询提示处直接按回车。每组都从重新运行程序开始,不能沿用上一组内存。
| 组 | 录入 → 取消 → 查询 | 应看到的关键证据 |
|---|---|---|
| A | 王宁、陈晨、李悦、q → 陈晨 → 李悦 | 2 人;原序王宁、李悦;查询已报名 |
| B | “ 陈晨 ”、陈晨、q → 空 → 陈晨 | 清洗后重复被拒;1 人 |
| C | q → 空 → 陈晨 | 0 人;暂无成员;查询未报名 |
| D | 陈晨、q → 王宁 → 王宁 | 取消失败;原名单仍含陈晨 |
| E | 空白姓名、“ Q ” → 空 → 空 | 空姓名失败;大写 Q 结束;0 人 |
| F | 陈晨、q → 陈晨 → 陈晨 | 取消后 0 人;查询未报名 |
| G | 王宁、李悦、陈晨、q → 空 → 空 | 原序与排序展示不同;展示后原序不变 |
| H | 至少五个不同姓名、q → 空 → 空 | 人数与实际名单一致,编号从 1 开始 |
若空表访问时出现 IndexError,找未经检查的索引;取消不存在的姓名出现 ValueError,找是否跳过了成员判断;名单突然变成 None,找是否把 append() 或 remove() 的返回值赋回变量;排序后原名单变了,找是否误用了 sort()。先复现最短输入,再修正并重跑原失败组。
6.9 练习与个人任务
先预测,再揭示
- 索引和人数
members = ["陈晨", "王宁"]时,members[1]与len(members)是什么?查看答案
分别是王宁与 2。索引从 0 开始,长度是元素总数。
- 重复输入
清洗后
name已在名单中,再执行新增分支,人数应怎样变化?查看答案
不变。重复检查应挡在
append()之前。 - 新结果还是原地修改
执行
display = sorted(members)后,members的顺序会变吗?查看答案
不会;
display是新列表。members.sort()才原地改变。
独立完成名单管理器
每位学生独立编写、运行和解释自己的程序;同伴临时配对只做黑盒测试,不合并源文件。页面示例帮助理解操作顺序,个人提交必须有自己的测试证据。
最低验收要求
- 提交
w06_学号_member_list.py:从空名单开始,录入至 q,随后一次取消、一次查询和最终展示。 - 空姓名、重复姓名、不存在成员的取消都要有明确提示;查询不改变名单。
- 提交 A—H 八组“预期—实际—结论—修正后复测”,至少包含五位不同姓名、空表、重复输入。
- 限时个人修改另存文件:容量改为 3。新增时先判空、再判重复、最后判满;满额不结束录入,还能继续读到 q。
- 提交同伴互测单与 80—150 字复盘,能解释原顺序和排序展示为何不同。
本章不引入嵌套列表、列表推导式、字典、自定义函数或文件保存。程序退出后,内存中的名单不会自动留存;同名冲突也尚未解决。这些是明确的阶段边界,不是通过假数据掩盖的问题。
章末检查
- 我能说出元素值、索引、展示编号的区别。
- 我会在
remove()前检查成员存在。 - 我不用方法返回值覆盖原列表。
- 人数来自当前名单,查询和排序展示不改变原顺序。
延伸阅读:廖雪峰《Python教程》在线 PDF中的“list 和 tuple”,以及菜鸟教程 Python 3 列表。本章实例与验收规则按 ClubHub 教学项目重新组织。