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

第 7 章 · 从名单走向身份与关系

字典、集合与活动数据

第 6 章用姓名做唯一标识,遇到两位都叫“陈晨”的同学就会误判重复。现在用学号查姓名,用一个字典描述活动,用两个集合分别保存报名与签到学号。关键不只是换了容器,而是每次操作都维护“谁是谁、谁已报名、谁可签到”的规则。

预计阅读
65 分钟
上机实践
90 分钟
前置知识
第 1—6 章
本章产出
个人活动数据程序

学完本章,你应该能够

  • 用字典的键和值表达学号到姓名、字段名到活动信息的映射。
  • 完成字典增删改查与遍历,区分直接索引和 get() 的结果。
  • 正确建立空集合,用 add()、discard() 和成员判断管理唯一学号。
  • 按“未知 → 重复 → 满额”和“未知 → 未报名 → 已签到”顺序守住两类操作。
  • 用具体学号检验签到与报名的一致性,独立完成八组测试和结构选型说明。

7.1 一种数据结构不能回答所有问题

第 6 章的姓名列表能保留顺序,但无法区分同名同学;本章没有报名先后展示要求,重点是身份映射与唯一关系。四位虚构成员中,20260001 和 20260002 都叫陈晨,仍是两个不同的人。学号必须保留为字符串,不把它当作可算术运算的数字。

问题选择为什么
学号对应哪位成员?字典 members用唯一学号作键,查到姓名
活动有哪些属性?字典 activity用 code/name/capacity 作字段名
哪些学号已报名?集合 registrations同一学号只保留一次
哪些学号已签到?集合 checkins身份唯一,且必须先属于报名集合

本章固定只有一个免费活动,不做费用计算、多活动嵌套或持久化。集合不保证报名顺序;若要稳定显示,用 sorted() 得到临时列表,但不能把排序后的结果说成报名先后。

7.2 字典:由学号查姓名

字典用花括号包围“键: 值”对。members[student_id] 按键取值,不是列表索引;student_id in members 检查的是键是否存在。直接访问不存在的键会得到 KeyError,因此业务操作应先做成员守卫。

student_mapping.py
members = {"20260001": "陈晨", "20260002": "陈晨"}
print(members["20260001"])
print("20260002" in members)
print("陈晨" in members)
print(members.get("99999999"))
运行结果
陈晨
True
False
None

两个键不同,值可以相同。get() 在键不存在时默认返回 None,也可给默认值;它适合安全读取可选信息,但不能因为用了 get() 就跳过“未知学号不能报名”的业务检查。"陈晨" in members 为 False,因为它是值,不是键。

7.3 活动字典的增、删、改、查与遍历

活动字段名是固定的教学约定:code、name、capacity。为某个键赋值,若键已存在就是修改,不存在就是新增。用 del 可删除一个已存在的键;但基础活动记录必须保留三个必需字段,删除示例只在临时字段上做。

activity_fields.py
activity = {"code": "EV-0021", "name": "Python体验营", "capacity": 2}
activity["capacity"] = 3
activity["note"] = "教学演示"
print(activity.get("venue", "地点未定"))
for key, value in activity.items():
    print(f"{key}: {value}")
del activity["note"]
print("note" in activity)
运行结果
地点未定
code: EV-0021
name: Python体验营
capacity: 3
note: 教学演示
False

items() 让每一轮同时得到键和值。这里为了展示“修改”把容量临时改成 3;后面的正式程序仍按预置规则使用容量 2。字典内容一旦修改,后续读取就会看到新值;不要把示例修改偷偷带入八组固定测试。

7.4 集合:同一学号只能出现一次

空集合必须写 set();空花括号 {} 是空字典。集合的 add() 用于加入,discard() 用于删除,in 用于成员判断。集合本身会去重,但业务程序仍要在重复报名时明确提示,不能只让 add() 静默无事。

set_basics.py
registrations = set()
registrations.add("20260001")
registrations.add("20260001")
registrations.add("20260002")
print(len(registrations))
print("20260002" in registrations)
registrations.discard("99999999")
print(sorted(registrations))
运行结果
2
True
['20260001', '20260002']

discard() 删除不存在的值不会报错;但“没有报错”不等于“取消成功”。若要向学生报告操作结果,仍要先判断对象是否已报名。第 8 章会用它处理取消并同步撤销签到。本章只用 add() 完成报名和签到。

7.5 签到不是另一份随意增长的名单

报名集合与签到集合都存学号,但含义不同:签到者必须先在报名集合中。每次成功签到后,checkins 中的每个学号都应仍在 registrations 中。若只看两个集合的长度,无法发现“签到的是另一个人”这种关系错误。

checkin_guard.py
registrations = {"20260001"}
checkins = set()
student_id = "20260002"
if student_id not in registrations:
    print("签到失败:请先报名")
else:
    checkins.add(student_id)
print(sorted(checkins))
运行结果
签到失败:请先报名
[]

完整报名判断的顺序是“学号不在成员字典 → 已在报名集合 → 名额已满 → 成功加入”;重复应先于满额检查,这样即使已满,也能准确回答“请勿重复报名”。签到判断是“未知学号 → 未报名 → 已签到 → 成功加入”。所有拒绝路径都不改变两个集合。

用关系而非只用数量验收

若报名集合是 {"20260001"},签到集合却是 {"20260002"},两边人数都是 1,仍然是错误状态。要逐个检查签到学号是否已经报名,而不是仅比较人数大小。

7.6 完成活动字典与报名签到程序

正式程序使用四位虚构成员、一个容量 2 的活动,固定读取三次报名和两次签到。每轮都读取一个学号,包括已满额时的第三次报名;否则无法给重复或未知学号提供准确反馈。输入先 strip(),空白与未知学号统一按“不存在”处理。

activity_data.py
# 第7章:活动字典与报名、签到集合
# 教学虚构数据;学号是字符串,同名不等于同一人。
members = {
    "20260001": "陈晨",
    "20260002": "陈晨",
    "20260003": "王宁",
    "20260004": "李悦",
}
activity = {"code": "EV-0021", "name": "Python体验营", "capacity": 2}
registrations = set()
checkins = set()

print(f"活动:{activity['code']} {activity['name']}")
for sequence in range(3):
    student_id = input("报名学号:").strip()
    if student_id not in members:
        print("报名失败:学号不存在")
    elif student_id in registrations:
        print("报名失败:请勿重复报名")
    elif len(registrations) >= activity["capacity"]:
        print("报名失败:名额已满")
    else:
        registrations.add(student_id)
        print(f"报名成功:{student_id} {members[student_id]}")

for sequence in range(2):
    student_id = input("签到学号:").strip()
    if student_id not in members:
        print("签到失败:学号不存在")
    elif student_id not in registrations:
        print("签到失败:请先报名")
    elif student_id in checkins:
        print("签到失败:请勿重复签到")
    else:
        checkins.add(student_id)
        print(f"签到成功:{student_id} {members[student_id]}")

print(f"报名人数:{len(registrations)}")
print(f"签到人数:{len(checkins)}")
print(f"剩余名额:{activity['capacity'] - len(registrations)}")
print(f"报名学号:{sorted(registrations)}")
print(f"签到学号:{sorted(checkins)}")

例如报名依次输入 20260001、20260002、20260003,签到依次输入前两位学号:两位同名陈晨都能报名,第三位因名额已满失败;最终报名 2、签到 2、剩余 0。两集合按学号排序输出,仅为了结果稳定,不表达报名先后。

  1. 1
    先确认身份

    学号不存在时不访问 members[student_id],避免 KeyError。

  2. 2
    成功路径才更新

    只有全部守卫通过才调用 add(),拒绝路径不改状态。

  3. 3
    从集合读出统计

    报名、签到人数分别由 len() 得到,剩余名额由容量减报名人数得到。

7.7 八组规则测试:每组重新运行

表中每组都输入五次:前三次是报名,后两次是签到;“回车”表示空输入。先预测完整最终状态,再运行并记录实际提示和两个具体学号集合。

组三次报名 → 两次签到关键预期
A01、02、03 → 01、02两位陈晨均成功;03 满额;报名 2、签到 2
B01、01、03 → 01、01重复报名、重复签到各被拒;签到 1
C未知、01、01 → 02、01未知被拒;02 未报名;报名 1、签到 1
D回车、回车、回车 → 回车、回车两集合为空;剩余名额 2
E“ 01 ”、02、03 → “ 01 ”、02首尾空白被清洗;报名、签到均 2
F01、02、03 → 未知、03未知签到、未报名签到被拒;签到 0
G01、02、01 → 01、02满额时第三次重复仍报“请勿重复报名”
H04、03、04 → 04、03最终显示按学号排序为 03、04

表中 01—04 是 20260001—20260004 的阅读缩写,实际输入必须是完整八位学号;“未知”可输入 99999999。若未知学号触发 KeyError,检查字典访问是否早于身份守卫;若重复时错误报满额,检查判断顺序;若集合内容与人数矛盾,检查是否维护了多余的独立计数器。

7.8 练习与个人任务

先预测,再揭示

  1. 键还是值

    两位陈晨在字典中是重复键吗?"陈晨" in members 会得到什么?

    查看答案

    不是。键是不同的学号,值可以相同;姓名不在键中,结果为 False。

  2. 空容器

    {} 与 set() 分别创建什么?

    查看答案

    前者是空字典,后者才是空集合。

  3. 人数与关系

    报名为 01,签到为 02,为什么“两个集合人数都是 1”仍不能算通过?

    查看答案

    签到的具体学号 02 不在报名集合中,违反“先报名后签到”。

第 7 周 · 个人机房任务

独立实现活动数据程序

每位学生维护自己的四份数据并提交源代码、测试表与解释;同伴可以提供测试输入,但不替代个人判断或提交。

最低验收要求

  • 提交 w07_学号_activity_sets.py,保留四位虚构成员、一个容量 2 的免费活动、两份初始空集合。
  • 固定三次报名、两次签到;即使满额也读完输入,不加 q 口令或菜单。
  • 按规定优先级给出拒绝原因;只有成功路径调用 add(),用 len() 和 sorted() 汇总。
  • 提交 A—H 八组“预期—实际—结论—修正后复测”,说明两位同名者为何都能报名。
  • 限时修改另存文件:在汇总后用 for 遍历排序后的报名学号,逐个判断不在签到集合者,显示未签到人数与名单;不另存一份长期同步的集合。
  • 提交 80—150 字结构选型与错误复盘,解释字典、集合各自解决了什么问题。
下载完整演示代码

本章不引入嵌套字典、推导式、复杂集合运算、自定义函数或文件。第 8 章才把各操作装入持续运行的菜单;现在固定轮次更便于先验证两集合的规则。

章末检查

  • 我用学号作键,不拿姓名代替身份。
  • 我知道字典 get() 与直接索引的差别。
  • 我能解释空字典和空集合的写法。
  • 我检查签到学号属于报名集合,而非只比较人数。

延伸阅读:廖雪峰《Python教程》在线 PDF中的“dict 和 set”,以及菜鸟教程 Python 3 字典、Python 3 集合。项目规则以本章的虚构数据和课程主数据为准。