前面我们已经学过列表、元组、字典。
它们各有各的用处,但有一种需求,在实际开发里出现得特别频繁:
去重。
比如一份用户名单里,有重复名字。 一批商品标签里,有重复标签。 一堆日志编号里,有重复编号。 一个爬虫抓下来的链接列表里,也可能重复一大堆。
如果你还用列表硬着头皮一个个判断,代码不但啰嗦,而且效率不高。
这时候,集合就该出场了。
集合这个东西,刚学的时候很多人会觉得它存在感不强。可一旦开始做数据处理,你会很快发现:
它简直就是为 去重 和 快速判断某个值在不在 而生的。
这一章,我们就把集合彻底讲明白。
一、什么是集合
在 Python 里,集合的名字叫 set。
它也是一种容器,可以存放多个数据。 但它和列表、元组最大的区别有两个:
集合里的元素不能重复 集合是无序的
先看一个最简单的例子:
nums = {1, 2, 3, 4}print(nums)print(type(nums))
输出:
{1, 2, 3, 4}<class 'set'>
这就是一个集合。
它外面用的是大括号,看上去和字典有点像,但它不是字典。
字典长这样:
{'name': '张三', 'age': 18}
里面是 键:值 的结构。
而集合长这样:
{1, 2, 3, 4}
里面只有值,没有键。
所以你可以这样记:
大括号里如果是 成对的键值,那是字典 大括号里如果只是 一堆单独的值,那是集合
二、集合最核心的特点:自动去重
这是集合最有代表性的能力。
看代码:
nums = {1, 2, 3, 2, 1, 4, 3}print(nums)
输出:
{1, 2, 3, 4}
你明明写了重复的值,但最后打印出来时,重复项自动没了。
这就是集合最核心的特性:
只保留不重复的元素
很多人第一次看到这里,会觉得有点神奇。 其实你不用一开始就深究底层原理,先把它当成集合的天然规则就行。
你往集合里放重复值,它不会报错,也不会存两份。 它只留一份。
所以集合特别适合做这类事情:
名单去重 标签去重 关键词去重 订单编号去重 链接去重
一句话概括:
只要你脑子里出现 去重 这两个字,就要想到 set。
三、集合为什么说是无序的
先看这个例子:
nums = {10, 3, 8, 1, 6}print(nums)
你可能会发现,输出顺序和你写进去的顺序不一定完全一样。
这就是因为集合是无序的。
这里的无序,不是说它乱七八糟完全不能用,而是说:
集合不靠位置来管理元素
也正因为如此,集合不像列表那样支持索引。
你不能这样写:
nums = {10, 20, 30}print(nums[0])
这会报错。
为什么?
因为集合根本就没有 第 0 个、第 1 个 这种概念。 它只关心某个元素在不在,不关心它排第几。
这一点非常重要。
列表的思维是:
第几个位置放了什么
集合的思维是:
某个值有没有出现
这两个方向完全不同。
四、空集合怎么写
这里有个新手特别容易踩坑的地方。
你可能会很自然地写:
data = {}
然后以为自己创建了一个空集合。
其实不是。
data = {}print(type(data))
输出:
<class 'dict'>
空的大括号,在 Python 里默认表示空字典,不是空集合。
那空集合怎么写?
要用:
data = set()print(type(data))
输出:
<class 'set'>
所以这一点一定记住:
{} 是空字典set() 才是空集合
这是集合入门阶段最经典的坑之一。
五、集合里能放什么类型的数据
集合里的元素必须是不可变类型。
你现在先记最常见的结论就够了:
数字可以放 字符串可以放 元组通常可以放 列表不能放 字典不能放
比如下面这些都没问题:
s = {1, 2, 3, 'hello', 'python'}print(s)
但如果你这样写:
s = {1, 2, [3, 4]}
就会报错。
因为列表是可变的,不能作为集合元素。
你可以简单理解成:
集合要求每个元素都得是稳定的 如果一个东西自己还会变来变去,集合就不好管理它
初学阶段记住这条就够用了。
六、集合和列表,看着都能装多个值,到底差在哪
这个问题很关键。
列表和集合都能装多个元素,但设计目标完全不同。
列表更适合:
保留顺序 允许重复 按位置访问 需要频繁按索引处理数据
集合更适合:
去重 快速判断元素是否存在 不关心顺序 不需要按位置操作
看一个对比例子。
列表:
names = ['张三', '李四', '张三', '王五']print(names)
输出:
['张三', '李四', '张三', '王五']
重复的内容会原样保留。
集合:
names = {'张三', '李四', '张三', '王五'}print(names)
输出:
{'张三', '李四', '王五'}
重复项会自动消失。
所以到底选哪个,不是看谁语法酷,而是看你的数据需求是什么。
如果你要的是一份完整记录,包括重复项,那就用列表。 如果你只关心有哪些不同的值,那就用集合。
七、最常见的集合用途:给列表去重
这一招非常实用,也几乎是所有人学集合时最先用到的场景。
比如现在有一个列表:
names = ['张三', '李四', '张三', '王五', '李四']
我们想去掉重复项,可以直接转成集合:
names = ['张三', '李四', '张三', '王五', '李四']result = set(names)print(result)
输出:
{'张三', '李四', '王五'}
这就完成去重了。
不过你要注意,转成集合之后,结果是集合类型,不再是列表。
如果你最终还想要列表,可以再转回来:
names = ['张三', '李四', '张三', '王五', '李四']result = list(set(names))print(result)
这样就得到了一个去重后的列表。
这招在日常数据处理中非常常见。
比如:
ids = [101, 102, 101, 103, 102, 104]unique_ids = list(set(ids))print(unique_ids)
这就是最典型的批量去重思路。
不过这里要提醒一句:
用 set() 去重后,原来的顺序通常不会保留。
如果你既想去重,又特别在意原顺序,后面要用别的方法。 但在入门阶段,先把 set 去重这个核心能力掌握住最重要。
八、如何往集合里添加元素
集合没有 append(),因为那是列表的方法。
集合添加元素,用的是 add()。
s = {1, 2, 3}s.add(4)print(s)
输出:
{1, 2, 3, 4}
如果你添加的是一个已经存在的元素,会怎么样?
s = {1, 2, 3}s.add(2)print(s)
输出还是:
{1, 2, 3}
不会报错,也不会多出一个 2。
这再次说明,集合天然不允许重复。
你往里加一个已经有的东西,它就当没发生过。
这在很多业务场景里特别省事。 你不用自己先写 if 判断有没有重复,集合帮你处理掉了。
九、一次添加多个元素:update()
如果你一次想加入多个值,可以用 update()。
s = {1, 2, 3}s.update([3, 4, 5, 6])print(s)
输出:
{1, 2, 3, 4, 5, 6}
这里传进去的是一个列表,也可以是元组、集合等可迭代对象。
update() 会把里面的元素一个个拿出来,加入到原集合中。 重复的会自动忽略,不重复的会加进去。
这一点很像批量合并。
比如你有一批旧标签,又来了一批新标签:
tags = {'Python', '爬虫'}tags.update(['数据分析', 'Python', '自动化'])print(tags)
最后 Python 不会重复出现,集合会自动帮你合并成不重复的一组标签。
十、删除元素:remove() 和 discard()
集合删除元素时,常见有两个方法:
remove()discard()
先看 remove():
s = {1, 2, 3, 4}s.remove(3)print(s)
输出:
{1, 2, 4}
如果你删的是不存在的元素,会报错。
s.remove(10)
这就会出问题。
再看 discard():
s = {1, 2, 3, 4}s.discard(3)print(s)
一样能删除。
但如果删的是不存在的元素:
s.discard(10)
它不会报错,程序会安静地继续往下走。
所以这两个方法的区别可以这样记:
remove() 比较严格,删不到就报错discard() 比较温和,删不到也没事
在实际开发里,如果你不确定某个元素一定存在,discard() 会更稳一些。
十一、pop():随机删除一个元素
集合也有 pop(),但它和列表的 pop() 很不一样。
列表的 pop() 通常是按位置删。 集合没有位置概念,所以集合的 pop() 是随机拿走一个元素。
s = {1, 2, 3, 4}x = s.pop()print(x)print(s)
你会发现每次拿走的未必都一样。
因为集合本来就是无序的。 所以这个方法你知道有就行,入门阶段不用太依赖它做核心逻辑。
如果你明确想删某个指定元素,还是优先用 remove() 或 discard()。
十二、clear():清空整个集合
这一点和字典、列表的思路差不多。
s = {1, 2, 3}s.clear()print(s)
输出:
set()
集合还在,只是里面空了。
十三、判断某个元素在不在集合里
这又是集合特别强的一点。
看例子:
s = {'Python', 'Java', 'Go'}print('Python'in s)print('C++'in s)
输出:
TrueFalse
这个判断写法看起来和列表一样,但集合在这种场景下通常更高效。
也就是说:
如果你经常要做 某个值在不在 这种判断,集合往往比列表更合适。
比如用户名是否已存在:
users = {'tom', 'jerry', 'alice'}if'tom'in users: print('用户名已存在')
比如某个商品编号是否出现过:
product_ids = {1001, 1002, 1003}if1002in product_ids: print('找到了')
集合在这种快速查找场景里,非常好用。
十四、为什么说集合查重和判断存在特别高效
这一节不讲太深的底层原理,只讲你能听懂的版本。
列表判断某个元素在不在,很多时候得从前往后一个个看。
比如你问:
这个值在列表里吗
程序通常要看第一个、第二个、第三个……慢慢找。
但集合不是这么干的。 它天生就为 快速判断某个元素是否存在 设计了更适合的结构。
所以当数据量变大时,这种差距会越来越明显。
你可以先把它理解成:
列表更像排队找人 集合更像查名册
排队一个个看,当然慢一点。 查名册直接定位,通常更快。
所以集合的关键词,不只是 去重,还有 快速查找。
十五、遍历集合
集合当然也可以遍历。
s = {'Python', 'Java', 'Go'}for item in s: print(item)
这没问题。
但你要记住,因为集合无序,所以遍历出来的顺序不一定固定。
如果你的逻辑特别依赖顺序,那集合可能就不适合你。
这一点非常重要。
比如你要输出排行榜、打印成绩单、按先后顺序展示数据,那就不要指望集合。 集合不是干这个的。
它更适合做 去重、筛选、判断存在 这些事情。
十六、集合之间的运算,才是它真正厉害的地方
前面那些去重、添加、删除,已经很实用了。 但集合还有一类特别经典的操作:
集合和集合之间做运算
比如两批用户,找共同关注的人。 两组标签,找重合部分。 两个班级名单,找交集、并集、差集。
这时候,集合会非常顺手。
十七、交集:两边都有的内容
交集,就是两个集合共同拥有的元素。
a = {1, 2, 3, 4}b = {3, 4, 5, 6}print(a & b)print(a.intersection(b))
输出:
{3, 4}{3, 4}
这表示 3 和 4 同时出现在两个集合里。
现实里很容易理解。
比如:
A 班报名名单 B 班报名名单 想找两个班都报名了的人
这就是交集。
十八、并集:把两边所有元素合在一起并去重
并集,就是两个集合所有元素合起来,重复的只保留一份。
a = {1, 2, 3}b = {3, 4, 5}print(a | b)print(a.union(b))
输出:
{1, 2, 3, 4, 5}{1, 2, 3, 4, 5}
这个特别适合合并标签、合并名单、合并编号集合。
比如一份老客户名单和一份新客户名单,你想得到全部客户,并且不重复,就很适合并集。
十九、差集:只在一边出现,在另一边没有
差集,就是找出只属于其中一边的元素。
a = {1, 2, 3, 4}b = {3, 4, 5}print(a - b)print(a.difference(b))
输出:
{1, 2}{1, 2}
表示在 a 里有,但在 b 里没有的元素。
这个思路在业务里很常见。
比如:
昨天的用户集合 今天的用户集合 想找今天新增了哪些用户
或者:
系统允许访问的权限集合 用户实际拥有的权限集合 想找还缺哪些权限
差集就派上用场了。
二十、对称差集:只保留不重合的部分
这个名字听起来有点拗口,但意思不难。
a = {1, 2, 3, 4}b = {3, 4, 5, 6}print(a ^ b)print(a.symmetric_difference(b))
输出:
{1, 2, 5, 6}{1, 2, 5, 6}
也就是:
去掉共同部分 只保留各自独有的部分
你现在不一定会马上用到,但知道有这个能力就够了。 等以后处理两批数据差异时,会很有用。
二十一、一个特别贴近实际的小案例:网站访问去重
假设你拿到一批访问用户名单:
visits = ['tom', 'jerry', 'tom', 'alice', 'bob', 'alice', 'tom']
现在你想知道:
一共有多少个不同用户来过 分别是谁
代码可以这样写:
visits = ['tom', 'jerry', 'tom', 'alice', 'bob', 'alice', 'tom']user_set = set(visits)print('去重后用户集合:', user_set)print('独立用户数:', len(user_set))
这样一下就出来了。
如果不用集合,你得写循环、写判断、手动维护去重逻辑。 而集合直接把问题简化了。
这就是 Python 容器类型真正迷人的地方:
合适的工具一选对,很多问题一下就轻了。
二十二、再看一个场景:共同好友
假设小明的好友有:
friends_a = {'张三', '李四', '王五', '赵六'}
小红的好友有:
friends_b = {'王五', '赵六', '小刚'}
想找共同好友:
friends_a = {'张三', '李四', '王五', '赵六'}friends_b = {'王五', '赵六', '小刚'}common = friends_a & friends_bprint(common)
输出:
{'王五', '赵六'}
如果不用集合,这种问题会写得麻烦很多。 但集合一上来,思路就特别自然。
二十三、集合最容易犯的几个错
先说第一个,也是最经典的。
很多人写空集合时,写成:
s = {}
这其实是字典,不是集合。
第二个坑,是把集合当列表用,想按索引取值:
s = {1, 2, 3}print(s[0])
这不行,因为集合无序,没有索引。
第三个坑,是往集合里放列表:
s = {1, 2, [3, 4]}
这会报错,因为列表不能作为集合元素。
第四个坑,是以为 set 去重后还能保留原顺序。 大多数情况下不能指望这一点。
第五个坑,是分不清 remove() 和 discard()。 一个删不到会报错,一个删不到也没事。
这些坑你现在提前知道,后面写代码时就能少踩很多雷。
二十四、集合和字典,看着都像大括号,怎么快速分辨
这一点值得再强调一次。
字典:
{'name': '张三', 'age': 18}
特征是里面有冒号,有键和值。
集合:
{'张三', '李四', '王五'}
特征是里面只有值,没有冒号。
再补一个细节:
空字典是 {}空集合是 set()
这个区别,新手一定要牢牢记住。
二十五、什么时候该用集合
这里给你一个最实用的判断标准。
如果你遇到下面这些需求,优先考虑集合:
需要去重 需要快速判断某个值在不在 需要比较两批数据有没有重合 需要找共同部分、不同部分 不在意元素顺序
比如:
用户名是否重复 商品标签去重 统计不同访问用户 找两个班级的共同学生 找两份名单的差异
这些都非常适合集合。
二十六、什么时候不该用集合
反过来,如果你特别在意下面这些事,就别轻易用集合:
需要保留顺序 允许重复 需要按位置访问 需要切片 需要记录每个值出现了几次
比如成绩单、购物车顺序、聊天消息列表,这些更适合列表。 集合不是万能容器,它只是特别擅长某些问题。
真正会写代码的人,不是见什么都用一种结构,而是能根据问题选对结构。
二十七、练习题:把集合真正用起来
下面这些小练习,建议你自己敲一遍。
1. 定义一个集合,包含 1、2、3、4
s = {1, 2, 3, 4}print(s)
2. 把列表去重
nums = [1, 2, 2, 3, 4, 4, 5]result = set(nums)print(result)
3. 给集合添加一个新元素 6
s = {1, 2, 3}s.add(6)print(s)
4. 删除集合中的 2
s = {1, 2, 3, 4}s.remove(2)print(s)
5. 判断 3 是否在集合中
s = {1, 2, 3, 4}print(3in s)
6. 求两个集合的交集
a = {1, 2, 3, 4}b = {3, 4, 5, 6}print(a & b)
7. 求两个集合的并集
print(a | b)
8. 求两个集合的差集
print(a - b)
你把这些题自己敲一遍,集合最核心的感觉就基本建立起来了。
二十八、本章小结
这一章你最需要记住的,不是 set 这个单词,而是集合背后的用途。
集合是一种不重复、无序的数据容器。 它最擅长两件事:
去重 快速判断某个元素是否存在
你要记住的核心点有这些:
集合里的元素不能重复 集合没有索引,不能按位置取值 空集合要用 set() 创建 添加元素用 add()删除元素常用 remove() 或 discard()集合特别适合做交集、并集、差集运算
如果说列表是拿来装一排数据的, 字典是拿来描述一条信息的, 那集合更像一个筛子。
它会帮你把重复的滤掉,把重合的找出来,把差异的挑出来。
下一章我们继续讲 列表、元组、字典、集合,到底该怎么选。 到那一章,前面这些容器类型就会真正串起来,你会慢慢建立一种看见问题就知道该用什么数据结构的感觉。