集合
# 集合 (Set)
集合(Set)是 Python 中一种非常重要的数据结构,它源自数学中的集合概念。想象一个袋子,你只能往里面放独一无二的物品,而且袋子里的物品是杂乱无章的——这就是集合的精髓。
# 1. 什么是集合 (Set)?
集合是一个无序 (Unordered)、不重复 (Unique) 的元素集合。
- 无序性:集合中的元素没有固定的顺序。你放入的顺序不代表取出的顺序,因此集合不支持索引
[0]
或切片操作。 - 唯一性:集合会自动去除所有重复的元素,确保每个元素只出现一次。
- 可变性 (Mutable):你可以随时向集合中添加或删除元素(对于常规的
set
)。 - 元素要求:集合中的元素必须是不可变类型,例如:
int
,float
,str
,tuple
。列表list
、字典dict
和其他集合set
等可变类型不能作为集合的元素。
# 2. 创建集合
Python 提供了多种方式来创建集合。
# 2.1 使用花括号 {}
这是创建非空集合最直接的方式。
# 创建一个包含元素的集合,重复的元素会被自动忽略
fruits = {"apple", "banana", "cherry", "apple"}
print(f"创建的集合: {fruits}") # 输出: 创建的集合: {'banana', 'cherry', 'apple'} (注意:顺序可能不同)
1
2
3
2
3
# 2.2 使用 set()
构造函数
set()
函数可以将任何可迭代对象(如列表、元组、字符串)转换为集合,这是去重的最佳方式。
# 从列表创建集合 (自动去重)
my_list = [1, 2, 3, 2, 1]
my_set_from_list = set(my_list)
print(f"从列表创建: {my_set_from_list}") # 输出: 从列表创建: {1, 2, 3}
# 从字符串创建集合
my_set_from_str = set("hello")
print(f"从字符串创建: {my_set_from_str}") # 输出: 从字符串创建: {'e', 'o', 'l', 'h'}
# 从元组创建集合
my_tuple = (10, 20, 30)
my_set_from_tuple = set(my_tuple)
print(f"从元组创建: {my_set_from_tuple}") # 输出: 从元组创建: {10, 20, 30}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 2.3 创建空集合
新手陷阱:创建一个空集合必须使用
set()
函数,因为{}
在 Python 中用于创建空字典。
# 正确方式
empty_set = set()
print(f"空集合: {empty_set}, 类型: {type(empty_set)}") # 输出: 空集合: set(), 类型: <class 'set'>
# 错误方式
empty_dict = {}
print(f"空对象: {empty_dict}, 类型: {type(empty_dict)}") # 输出: 空对象: {}, 类型: <class 'dict'>
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3. 集合的基本操作
# 3.1 添加元素
add(element)
: 向集合中添加单个元素。如果元素已存在,则不做任何操作。update(iterable)
: 将一个可迭代对象中的所有元素批量添加到集合中。
skills = {"Python", "Java"}
print(f"初始集合: {skills}") # 输出: 初始集合: {'Python', 'Java'}
# 添加单个元素
skills.add("Go")
print(f"add('Go') 后: {skills}") # 输出: add('Go') 后: {'Java', 'Go', 'Python'}
# 再次添加已存在的元素,集合不变
skills.add("Java")
print(f"add('Java') 后: {skills}") # 输出: add('Java') 后: {'Java', 'Go', 'Python'}
# 批量添加多个元素
skills.update(["C++", "SQL"])
print(f"update(['C++', 'SQL']) 后: {skills}") # 输出: ... {'SQL', 'Java', 'Go', 'C++', 'Python'}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.2 删除元素
remove(element)
: 删除指定的元素。如果元素不存在,会抛出KeyError
异常。discard(element)
: 删除指定的元素。如果元素不存在,它会静默处理,不会报错。pop()
: 随机删除并返回一个元素。对空集合使用pop()
会抛出KeyError
。clear()
: 清空集合中的所有元素。
members = {"Alice", "Bob", "Charlie", "David"}
print(f"初始集合: {members}") # 输出: 初始集合: {'David', 'Charlie', 'Bob', 'Alice'}
# 使用 remove 删除存在的元素
members.remove("Bob")
print(f"remove('Bob') 后: {members}") # 输出: remove('Bob') 后: {'David', 'Charlie', 'Alice'}
# 使用 discard 删除不存在的元素 (安全)
members.discard("Eve") # 不会报错
print(f"discard('Eve') 后: {members}") # 输出: discard('Eve') 后: {'David', 'Charlie', 'Alice'}
# 尝试用 remove 删除不存在的元素 (会报错)
try:
members.remove("Frank")
except KeyError:
print("用 remove 删除 'Frank' 失败,因为它不存在。") # 输出: 用 remove 删除 'Frank' 失败,因为它不存在。
# 随机弹出一个元素
popped_member = members.pop()
print(f"被 pop 的成员: {popped_member}") # 输出: 被 pop 的成员: David (或其他任意一个)
print(f"pop() 后的集合: {members}") # 输出: pop() 后的集合: {'Charlie', 'Alice'}
# 清空集合
members.clear()
print(f"clear() 后的集合: {members}") # 输出: clear() 后的集合: set()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 4. 强大的集合运算
集合最强大的功能在于其丰富的数学运算,可以高效地处理数据关系。
# 准备两个集合
frontend_skills = {"HTML", "CSS", "JavaScript"}
backend_skills = {"Python", "SQL", "JavaScript"}
1
2
3
2
3
# 4.1 并集 (Union)
获取两个集合中的所有元素,并自动去重。
- 运算符:
|
- 方法:
.union()
# 并集
all_skills_op = frontend_skills | backend_skills
all_skills_method = frontend_skills.union(backend_skills)
print(f"并集 (运算符): {all_skills_op}") # 输出: ... {'CSS', 'JavaScript', 'SQL', 'HTML', 'Python'}
print(f"并集 (方法): {all_skills_method}") # 输出: ... {'CSS', 'JavaScript', 'SQL', 'HTML', 'Python'}
1
2
3
4
5
6
2
3
4
5
6
# 4.2 交集 (Intersection)
获取两个集合中共同存在的元素。
- 运算符:
&
- 方法:
.intersection()
# 交集
common_skills_op = frontend_skills & backend_skills
common_skills_method = frontend_skills.intersection(backend_skills)
print(f"交集 (运算符): {common_skills_op}") # 输出: 交集 (运算符): {'JavaScript'}
print(f"交集 (方法): {common_skills_method}") # 输出: 交集 (方法): {'JavaScript'}
1
2
3
4
5
6
2
3
4
5
6
# 4.3 差集 (Difference)
获取存在于第一个集合但不存在于第二个集合的元素。
- 运算符:
-
- 方法:
.difference()
# 差集
frontend_only_op = frontend_skills - backend_skills
backend_only_method = backend_skills.difference(frontend_skills)
print(f"只会前端的技能: {frontend_only_op}") # 输出: 只会前端的技能: {'HTML', 'CSS'}
print(f"只会后端的技能: {backend_only_method}") # 输出: 只会后端的技能: {'SQL', 'Python'}
1
2
3
4
5
6
2
3
4
5
6
# 4.4 对称差集 (Symmetric Difference)
获取只存在于其中一个集合,但不是两个集合共有的元素。
- 运算符:
^
- 方法:
.symmetric_difference()
# 对称差集
exclusive_skills_op = frontend_skills ^ backend_skills
exclusive_skills_method = frontend_skills.symmetric_difference(backend_skills)
print(f"非共有技能 (运算符): {exclusive_skills_op}") # 输出: ... {'SQL', 'Python', 'HTML', 'CSS'}
print(f"非共有技能 (方法): {exclusive_skills_method}") # 输出: ... {'SQL', 'Python', 'HTML', 'CSS'}
1
2
3
4
5
6
2
3
4
5
6
# 5. 子集与超集判断
- 子集 (Subset):
A.issubset(B)
或A <= B
,判断集合 A 的所有元素是否都在集合 B 中。 - 超集 (Superset):
A.issuperset(B)
或A >= B
,判断集合 A 是否包含集合 B 的所有元素。 - 真子集/真超集: 使用
<
和>
判断,要求是子集/超集且两个集合不相等。 - 不相交 (Disjoint):
A.isdisjoint(B)
,判断两个集合是否没有任何共同元素。
A = {1, 2, 3}
B = {1, 2, 3, 4, 5}
C = {1, 2}
D = {10, 20}
# 子集判断
print(f"A 是 B 的子集吗? {A.issubset(B)}") # 输出: A 是 B 的子集吗? True
print(f"C <= A: {C <= A}") # 输出: C <= A: True
# 超集判断
print(f"B 是 A 的超集吗? {B.issuperset(A)}") # 输出: B 是 A 的超集吗? True
print(f"B >= C: {B >= C}") # 输出: B >= C: True
# 真子集判断
print(f"A 是 B 的真子集吗? {A < B}") # 输出: A 是 B 的真子集吗? True
print(f"A < A: {A < A}") # 输出: A < A: False
# 不相交判断
print(f"A 和 D 是否不相交? {A.isdisjoint(D)}") # 输出: A 和 D 是否不相交? True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 6. 原地更新运算 (In-place Updates)
与第4节的运算不同,这些运算会直接修改调用它们的集合本身,而不是返回一个新集合。它们通常在处理大型集合时更高效,因为避免了创建新对象的开销。
# 6.1 并集更新
- 运算符:
|=
- 方法:
update()
(已在3.1中介绍)
set1 = {1, 2, 3}
set2 = {3, 4, 5}
set1 |= set2 # 等价于 set1.update(set2)
print(f"并集更新后: {set1}") # 输出: 并集更新后: {1, 2, 3, 4, 5}
1
2
3
4
2
3
4
# 6.2 交集更新
- 运算符:
&=
- 方法:
.intersection_update()
set1 = {1, 2, 3}
set2 = {3, 4, 5}
set1.intersection_update(set2)
print(f"交集更新后: {set1}") # 输出: 交集更新后: {3}
1
2
3
4
2
3
4
# 6.3 差集更新
- 运算符:
-=
- 方法:
.difference_update()
set1 = {1, 2, 3}
set2 = {3, 4, 5}
set1 -= set2 # 等价于 set1.difference_update(set2)
print(f"差集更新后: {set1}") # 输出: 差集更新后: {1, 2}
1
2
3
4
2
3
4
# 6.4 对称差集更新
- 运算符:
^=
- 方法:
.symmetric_difference_update()
set1 = {1, 2, 3}
set2 = {3, 4, 5}
set1.symmetric_difference_update(set2)
print(f"对称差集更新后: {set1}") # 输出: 对称差集更新后: {1, 2, 4, 5}
1
2
3
4
2
3
4
# 7. 遍历与推导式
# 7.1 遍历集合
可以使用 for
循环遍历集合中的每一个元素,但请记住,遍历的顺序是不确定的。
fruits = {"apple", "banana", "cherry"}
print("--- 遍历集合 ---")
for fruit in fruits:
print(fruit)
# 可能的输出 (顺序不保证):
# cherry
# banana
# apple
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 7.2 集合推导式 (Set Comprehension)
这是一种简洁、高效地创建集合的方式,语法与列表推导式非常相似。
# 创建一个包含 0 到 9 中偶数的平方的集合
squares = {x**2 for x in range(10) if x % 2 == 0}
print(squares) # 输出: {0, 64, 4, 36, 16}
1
2
3
2
3
# 8. 不可变集合:frozenset
frozenset
是 set
的不可变版本。一旦创建,就不能再添加或删除任何元素。
- 为什么需要它? 因为集合的元素必须是不可变的,所以普通的
set
不能作为另一个set
的元素,也不能作为字典的键。frozenset
正好解决了这个问题。
# 创建 frozenset
frozen_skills = frozenset(["Python", "Java"])
print(frozen_skills) # 输出: frozenset({'Java', 'Python'})
# 尝试修改会报错
# frozen_skills.add("Go") # AttributeError: 'frozenset' object has no attribute 'add'
# 主要用途:作为字典的键
team_skills = {
frozenset({"Python", "SQL"}): "后端团队",
frozenset({"HTML", "CSS", "JavaScript"}): "前端团队"
}
print(team_skills[frozenset({"Python", "SQL"})]) # 输出: 后端团队
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
frozenset
支持所有不修改自身的集合操作,如.union()
,.intersection()
,in
判断等。
# 9. 总结与最佳实践
- 核心应用场景:
- 高效去重:将列表或其他可迭代对象转换为
set
是最快、最简洁的去重方法。 - 快速成员资格测试:判断一个元素是否在一个集合中 (
element in my_set
) 的速度远快于在列表中查找,尤其是在数据量大时。
- 高效去重:将列表或其他可迭代对象转换为
- 性能对比:
set
的成员资格测试平均时间复杂度为 O(1)。list
的成员资格测试平均时间复杂度为 O(n)。这意味着列表越大,查找越慢。
- 何时选择
set
:- 当你需要存储一系列唯一的项时。
- 当你关心的是元素是否存在,而不是它们的顺序时。
- 当你需要频繁进行数据去重或数学集合运算时。
编辑此页 (opens new window)
上次更新: 2025/07/23, 06:33:16