第14章 集合与组合结构
问你一个问题:手头有一串名单,里面有人重复报了两次名,怎么把重复的去掉?用昨天学的列表,得写循环一个个比对,麻烦。Python 里有个东西天生干这个——集合,一个"自动去重"的麻袋:你把一袋子数字倒进去,重复的自动消失,
[1, 2, 2, 3, 3, 3]倒进去就剩{1, 2, 3}。处理"有没有重复""有哪些公共元素"这类问题,集合最省事。今天先学它,再学本周真正的重头戏——列表套字典:一个列表里每个元素都是一个字典。它就像一张 Excel 表格:列表 = 整张表,每个字典 = 一行记录,字典的键 = 列名。为什么必须学它?因为从下周开始的所有项目——通讯录、记账本、网站后台——数据全都长这样。今天把它练熟,等于给后面的项目把地基打好。
集合:自动去重的"麻袋"
集合用花括号 {} 包起来,但里面不是键值对,是普通元素。核心特点就一个:自动去重——同一个元素只会出现一次。
和列表比一比,区别一目了然:
| 对比 | 列表 | 集合 |
|---|---|---|
| 写法 | [1, 2, 3] 方括号 |
{1, 2, 3} 花括号 |
| 重复元素 | 允许 | 不允许,自动去掉 |
| 有顺序吗 | 有(按下标取) | 没有(不能按下标取) |
nums = {1, 2, 2, 3, 3, 3}
print(nums) # {1, 2, 3}(重复的被自动去掉)
集合长什么样,能干什么、不能干什么:
a = {1, 2, 3}
b = {"苹果", "香蕉", "橙子"}
print(a) # {1, 2, 3}
print(b) # {'苹果', '香蕉', '橙子'}(顺序可能不同,集合无序)
# 集合不能按下标取
# print(a[0]) # 报错!TypeError: 'set' object is not subscriptable
# len 和 in 可以用
print(len(a)) # 3
print(2 in a) # True
输出:
{1, 2, 3}
{'苹果', '香蕉', '橙子'}
3
True
三个新手坑提前说:集合没有顺序,打印顺序可能和你写的不一样,别指望 {1,2,3} 里第 0 个是 1;空集合是 set(),不是 {}——{} 是空字典,这个错几乎人人踩过;集合里的元素不能是列表(列表可变,没法"去重"),放列表会报 TypeError: unhashable type: 'list'。
集合的基本操作:增删与判断
集合的增删方法和列表不一样,名字不同、行为也不同:
| 操作 | 集合方法 | 说明 |
|---|---|---|
| 加一个 | add(值) |
已存在的元素加了也没用(本来就一个) |
| 删一个 | remove(值) |
删不存在的会报错 |
| 数个数 | len(集合) |
元素个数 |
| 判断在不在 | 值 in 集合 |
True / False |
fruits = {"苹果", "香蕉"}
fruits.add("橙子") # 加一个
print(fruits) # {'苹果', '香蕉', '橙子'}
fruits.add("苹果") # 加重复的 → 没有变化
print(fruits) # {'苹果', '香蕉', '橙子'}
fruits.remove("香蕉") # 删一个
print(fruits) # {'苹果', '橙子'}
add 重复元素没效果,这正是"去重"的本体:
s = set() # 空集合(记住:不是 {})
s.add("苹果")
s.add("苹果") # 再加一次,没反应
s.add("香蕉")
print(s) # {'苹果', '香蕉'}
print(len(s)) # 2(不是 3,重复的被吞了)
输出:
{'苹果', '香蕉'}
2
三个容易错的:加元素用 add,不是 append——集合没有 append,用了报 AttributeError;删元素用 remove,删不存在的会报 KeyError,稳妥做法是先 in 判断;集合没有"按下标改"的说法,只有"加/减一个元素"。
交集与并集:两个集合做运算(了解即可)
集合之间能像数学课一样做运算:交集是"两边都有的",并集是"合在一起、去掉重复的"。本周能看懂、会用就行,不要求背得滚瓜烂熟:
| 符号 | 意思 | 例子 a={1,2,3} b={2,3,4} |
结果 |
|---|---|---|---|
a & b |
交集(两边都有) | a & b |
{2, 3} |
a \| b |
并集(合起来去重) | a \| b |
{1, 2, 3, 4} |
a - b |
差集(a 有 b 没有) | a - b |
{1} |
a = {1, 2, 3}
b = {2, 3, 4}
print(a & b) # {2, 3}(交集)
print(a | b) # {1, 2, 3, 4}(并集)
print(a - b) # {1}(差集,了解即可)
输出:
{2, 3}
{1, 2, 3, 4}
{1}
实际用起来,最典型的是找"两个人共同喜欢的水果":
xiaoming = {"苹果", "香蕉", "橙子"}
xiaohong = {"香蕉", "西瓜", "葡萄"}
print(xiaoming & xiaohong) # {'香蕉'}(两个人都喜欢的水果)
输出:
{'香蕉'}
有一点要拎出来说:& 和 | 只对集合有效,对列表直接报错(列表不支持);想对列表做交集,先转成集合。别把 |(并集)和 or(逻辑或)搞混——{1} | {2} 是集合运算,结果是集合。交集并集今天"会看会读"就行,重点还是下面的列表套字典。
去重实战:把列表里的重复数字找出来
集合最实用的场景:给列表去重。用 set(列表) 转一下,重复的自动消失;想变回列表,再 list() 转回来。
"找重复数字"有个巧妙的思路:如果列表里有重复,那么"原列表长度"一定大于"去重后集合的长度"。长度对不上,就说明有重复:
nums = [3, 1, 4, 1, 5, 9, 2, 6, 5]
print(len(nums)) # 9(原列表)
print(len(set(nums))) # 7(去重后)
判断"有没有重复",一行就行:
nums = [3, 1, 4, 1, 5, 9, 2, 6, 5]
if len(nums) == len(set(nums)):
print("没有重复数字")
else:
print("有重复数字!")
输出:
有重复数字!
但如果你想知道"哪些数字重复了",就得换招——遍历 + 计数:
nums = [3, 1, 4, 1, 5, 9, 2, 6, 5]
repeated = [] # 装重复的数字
for n in nums:
if nums.count(n) > 1 and n not in repeated:
repeated.append(n) # 出现超过 1 次,且还没记过
print(f"重复的数字有:{repeated}") # [1, 5]
输出:
重复的数字有:[1, 5]
三个细节:set(列表) 只是"转过去去重",不改变原列表——想保留结果要重新赋值 nums = list(set(nums));去重后的集合顺序会变(集合无序),介意顺序就用"方法二"逐个找;动手前先想清楚要什么输出——是"有没有重复"(长度对比),还是"哪些重复了"(逐个找),两个问题答案不同。
重头戏:列表套字典
今天真正的核心:列表套字典——一个列表里每个元素都是一个字典。它就是一张 Excel 表格:
- 整个列表 = 这张表
- 列表里每个字典 = 一行记录(一个同学 / 一件商品 / 一条通讯录)
- 字典的每个键 = 列名(姓名、成绩、电话……)
students = [
{"name": "小明", "score": 92},
{"name": "小红", "score": 85},
{"name": "小刚", "score": 78},
]
把它当 Excel 看就通了:students[0] 是第一行,students[0]["name"] 是第一行的"姓名"这一格。以后所有项目的数据都长这样,今天必须亲手敲一遍。
读:遍历每一行:
students = [
{"name": "小明", "score": 92},
{"name": "小红", "score": 85},
{"name": "小刚", "score": 78},
]
for stu in students: # stu 依次是每一行(一个字典)
print(stu["name"], stu["score"]) # 从行里取列
输出:
小明 92
小红 85
小刚 78
取一格、改一格:
students = [
{"name": "小明", "score": 92},
{"name": "小红", "score": 85},
{"name": "小刚", "score": 78},
]
print(students[0]) # {'name': '小明', 'score': 92}(第 1 行)
print(students[0]["name"]) # 小明(第 1 行的"姓名"列)
students[0]["score"] = 95 # 把小明(第 1 行)的成绩改成 95
print(students[0]) # {'name': '小明', 'score': 95}
输出:
{'name': '小明', 'score': 92}
小明
{'name': '小明', 'score': 95}
增:加一行用 append(字典),加一列是给某行加个键:
students = [
{"name": "小明", "score": 92},
{"name": "小红", "score": 85},
]
students.append({"name": "小丽", "score": 95}) # 新增一行
students[0]["city"] = "北京" # 给第一行加一列
print(students)
输出:
[{'name': '小明', 'score': 92, 'city': '北京'}, {'name': '小红', 'score': 85}, {'name': '小丽', 'score': 95}]
这里最常出的错,都在"层"上:
- 下标和键要分清:
students[0]是第 1 行(字典),students[0]["name"]才是一格(值),中间那层容易写漏。 - 遍历变量
stu是字典,stu["name"]用键取值,不是stu[0]——很多人在这里用下标,会报KeyError: 0。 - 加一行用
append(字典),加一列用某行["新键"] = 值,两个动作别搞混。
表格的统计:按列算数
数据装进表格后,最常见的需求是"按列算数":把所有成绩取出来,求平均、最高、最低。做法是遍历每一行,把要算的那一列抽出来:
students = [
{"name": "小明", "score": 92},
{"name": "小红", "score": 85},
{"name": "小刚", "score": 78},
]
scores = [] # 用来装抽出来的成绩
for stu in students:
scores.append(stu["score"]) # 抽每一行的 score 列
print(scores) # [92, 85, 78]
print(f"平均分:{sum(scores) / len(scores)}") # 平均分:85.0
print(f"最高分:{max(scores)}") # 最高分:92
print(f"最低分:{min(scores)}") # 最低分:78
输出:
[92, 85, 78]
平均分:85.0
最高分:92
最低分:78
"抽列"有更简短的写法——列表推导式(昨天看过,这里再用一次):
students = [
{"name": "小明", "score": 92},
{"name": "小红", "score": 85},
{"name": "小刚", "score": 78},
]
scores = [stu["score"] for stu in students] # 一行抽出所有成绩
print(scores) # [92, 85, 78]
print(sum(scores) / len(scores)) # 85.0
输出:
[92, 85, 78]
85.0
"抽列"三步走:先建空列表 → 循环里 append 这一列 → 循环外用 sum / max / min 算。抽出来的 scores 是数字列表,才能用 sum / max / min;忘记抽、直接对字典列表用 sum 会报 TypeError。推导式看不懂没关系,明天的成绩管理会再用一次,看多了自然懂。
随堂练习
练习一(必做):找重复
给列表 [3, 1, 4, 1, 5, 9, 2, 6, 5],用集合判断有没有重复数字,再用方法二找出重复的分别是哪些。
提示:判断用 len(nums) 和 len(set(nums)) 对比;找哪些重复用 for + count() + in 判断。
练习二:给成绩表加人改分
给 14.5 的 students 列表加 2 个新同学(append),把小明的成绩改成 95(students[0]["score"] = 95),再遍历打印全部。
提示:加人时注意格式——append({"name": "小丽", "score": 95}),花括号别丢。
练习三:算平均分
在练习二基础上,抽出所有成绩,打印平均分、最高分、最低分。
提示:scores = [stu["score"] for stu in students],然后 sum / max / min。
进阶题(可选):购物车升级
把周二写的购物车升级——每个商品存一个字典(名字 + 价格),用户输入"商品名 价格"(用空格分开),输入 q 退出,最后遍历打印每个商品和总价。
提示:拆分输入用 split();数据结构是 cart = [{"name": "牛奶", "price": 3.5}, ...];总价用循环累加 total += item["price"]。
写不出来时,让 AI 搭把手
场景:找重复数字写不出来,需要 AI 讲思路
提示词:
我是 Python 初学者,今天学集合。请帮我写一个程序:
列表 [3, 1, 4, 1, 5, 9, 2, 6, 5] 里,找出重复出现的数字。
先别给完整代码,先给我讲思路:怎么用集合判断有没有重复,
再告诉我用什么方法找出来。我是初学者,用大白话讲。
场景:列表套字典的某个操作不会写,比如"给表格加一行"
提示词:
我有这样一个列表套字典的数据:
students = [{"name": "小明", "score": 92}, {"name": "小红", "score": 85}]
我想:1. 加一个新同学 2. 修改小明的成绩 3. 遍历打印每个人的姓名和成绩。
请给我完整的示例代码,每行加注释,用最简单的写法。
常见报错速查
| 报错信息 | 什么意思 | 怎么办 |
|---|---|---|
TypeError: unhashable type: 'list' |
往集合里放列表 / 拿列表当键 | 集合元素和字典键只能用"简单值"(数字、字符串) |
TypeError: 'set' object is not subscriptable |
给集合按下标 [0] 取值 |
集合没有顺序,用 in 判断或用 for 遍历 |
AttributeError: 'set' object has no attribute 'append' |
集合没有 append 方法 |
集合加元素用 add |
KeyError: 0 |
遍历列表套字典时用了下标取列 | 字典取值得用键:stu["name"],不是 stu[0] |
收个尾
今天两个内容,一个轻一个重:集合自动去重,set(列表) 转一下重复全没,加元素用 add(没有 append),交集 &、并集 | 会看就行——这些是轻的。重的是列表套字典:列表 = 行、字典 = 一行、键 = 列名,一张数据表格。表格三件套动作要练熟:加行 append(字典)、改格 某行["键"] = 新值、抽列 [行["键"] for 行 in 表格]。
明天是本周综合练习:用今天学的列表套字典和前面的知识,完成两个完整程序——词频统计(统计一句话里每个词出现几次)和成绩管理(算平均分、最高分、最低分),全程 AI 协作,把这周学的东西真正串起来。