Python 集合(Set)系统学习指南

集合(Set)是 Python 中无序、不重复、元素可哈希的核心数据结构,核心价值在于快速去重、元素存在性判断、集合运算(交集/并集/差集),底层基于哈希表实现,查找效率极高(时间复杂度 O(1))。本文将从基础到进阶,全面覆盖集合的知识点。

一、集合的核心特性

在学习具体操作前,先明确集合的 3 个关键特性(决定了它的使用场景):

  1. 无序性:集合中的元素没有固定顺序,每次打印结果可能不同(Python 3.7+ 对字典有序优化,但集合仍无序)。
  2. 不重复性:集合会自动过滤重复元素,这是去重的核心用法。
  3. 元素可哈希:集合中的元素必须是不可变类型(如整数、字符串、元组、布尔值),不可变类型(列表、字典、集合)不能作为集合元素(会报 TypeError)。

哈希(Hash):将任意长度的数据转换为固定长度的唯一标识,不可变类型的哈希值固定,可变类型的哈希值会随内容变化,因此无法作为集合/字典的键。

二、集合的创建

Python 中创建集合有 2 种常用方式:{} 直接创建、set() 函数转换,注意空集合的创建细节。

1. 直接创建({}

语法:集合名 = {元素1, 元素2, ..., 元素n}

# 正常创建(自动去重)
s1 = {1, 2, 3, 2, "a"}
print(s1)  # 输出:{1, 2, 'a'}(顺序不确定)

# 重复元素被自动过滤
s2 = {1, 1, 2, 2}
print(s2)  # 输出:{1, 2}

# 元素必须可哈希(以下代码报错)
# s3 = {1, [2, 3]}  # TypeError: unhashable type: 'list'
# s4 = {1, {"name": "Tom"}}  # TypeError: unhashable type: 'dict'

2. set() 函数创建(转换型)

set() 可将可迭代对象(列表、元组、字符串、range 等)转换为集合,同样自动去重。
语法:集合名 = set(可迭代对象)

# 列表转集合(去重)
list1 = [1, 2, 2, 3]
s3 = set(list1)
print(s3)  # 输出:{1, 2, 3}

# 字符串转集合(按字符拆分,去重)
s4 = set("abac")
print(s4)  # 输出:{'a', 'b', 'c'}(顺序不确定)

# 元组转集合
tuple1 = (1, 2, 3, 3)
s5 = set(tuple1)
print(s5)  # 输出:{1, 2, 3}

# range 转集合
s6 = set(range(5))
print(s6)  # 输出:{0, 1, 2, 3, 4}

3. 空集合的创建

⚠️ 注意:{} 创建的是空字典,不是空集合!空集合必须用 set() 创建:

# 错误:创建的是空字典
empty_dict = {}
print(type(empty_dict))  # 输出:<class 'dict'>

# 正确:创建空集合
empty_set = set()
print(type(empty_set))  # 输出:<class 'set'>
print(empty_set)  # 输出:set()

三、集合的核心操作

集合的操作主要分为 5 类:添加元素、删除元素、查找元素、集合运算、其他常用操作,以下逐一详解。

(一)添加元素

集合提供 2 种添加方法:add()(添加单个元素)、update()(添加多个元素,接收可迭代对象)。

1. add():添加单个元素

语法:集合名.add(元素)

  • 若元素已存在,不报错也不重复添加;
  • 元素必须可哈希(不可变类型)。
s = {1, 2, 3}
s.add(4)  # 添加不存在的元素
print(s)  # 输出:{1, 2, 3, 4}

s.add(2)  # 添加已存在的元素(无效果)
print(s)  # 输出:{1, 2, 3, 4}

s.add(("a", "b"))  # 添加元组(可哈希)
print(s)  # 输出:{1, 2, 3, 4, ('a', 'b')}
2. update():添加多个元素

语法:集合名.update(可迭代对象)

  • 接收列表、元组、字符串、集合等可迭代对象,将其元素逐个添加到集合中;
  • 自动去重。
s = {1, 2, 3}
s.update([3, 4, 5])  # 添加列表(去重)
print(s)  # 输出:{1, 2, 3, 4, 5}

s.update((5, 6), "ab")  # 同时添加元组和字符串
print(s)  # 输出:{1, 2, 3, 4, 5, 6, 'a', 'b'}

s.update({7, 8})  # 添加集合
print(s)  # 输出:{1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b'}

(二)删除元素

集合提供 4 种删除方法:remove()discard()pop()clear(),核心区别在于“元素不存在时是否报错”和“删除逻辑”。

方法功能描述元素不存在时的行为
remove(x)删除集合中的指定元素 x报错 KeyError
discard(x)删除集合中的指定元素 x不报错(无效果)
pop()随机删除并返回集合中的一个元素(无序特性)集合为空时报错 KeyError
clear()清空集合(保留集合对象,元素为空)无报错(空集合调用也安全)
s = {1, 2, 3, 4, 5}

# 1. remove(x):元素不存在报错
s.remove(3)
print(s)  # 输出:{1, 2, 4, 5}
# s.remove(10)  # KeyError: 10(元素不存在)

# 2. discard(x):元素不存在无效果
s.discard(4)
print(s)  # 输出:{1, 2, 5}
s.discard(10)  # 无报错,集合不变
print(s)  # 输出:{1, 2, 5}

# 3. pop():随机删除(无序)
deleted = s.pop()
print("删除的元素:", deleted)  # 输出不确定(如 1)
print(s)  # 输出:{2, 5}(剩余元素)

# 4. clear():清空集合
s.clear()
print(s)  # 输出:set()

# 空集合调用 pop() 报错
# empty_set = set()
# empty_set.pop()  # KeyError: 'pop from an empty set'

(三)查找元素

集合没有索引(无序特性),因此无法通过 [索引] 访问元素,只能判断元素是否存在(核心用法)。

语法:元素 in 集合(存在返回 True,不存在返回 False

s = {1, 2, 3, "a"}
print(2 in s)  # 输出:True
print("b" in s)  # 输出:False
print(5 not in s)  # 输出:True(not in 表示不存在)

为什么集合的 in 判断比列表快?
列表是线性结构,查找元素需遍历所有元素(时间复杂度 O(n));集合是哈希表结构,通过元素的哈希值直接定位(时间复杂度 O(1)),数据量越大,效率差异越明显。

(四)集合运算(核心功能)

集合最强大的功能是集合运算(交集、并集、差集、对称差集),适用于数据筛选、去重、对比等场景,支持运算符方法两种调用方式(推荐运算符,更简洁)。

假设有两个集合:

a = {1, 2, 3, 4}  # 集合 A
b = {3, 4, 5, 6}  # 集合 B
1. 交集(& / intersection())
  • 含义:两个集合中共同存在的元素。
  • 语法:a & ba.intersection(b)
print(a & b)  # 输出:{3, 4}
print(a.intersection(b))  # 输出:{3, 4}
2. 并集(| / union())
  • 含义:两个集合中所有元素(自动去重)。
  • 语法:a | ba.union(b)
print(a | b)  # 输出:{1, 2, 3, 4, 5, 6}
print(a.union(b))  # 输出:{1, 2, 3, 4, 5, 6}
3. 差集(- / difference())
  • 含义:集合 A 中存在但集合 B 中不存在的元素(顺序影响结果)。
  • 语法:a - b(A 减 B)或 a.difference(b)
print(a - b)  # 输出:{1, 2}(A 有,B 没有)
print(b - a)  # 输出:{5, 6}(B 有,A 没有)
print(a.difference(b))  # 输出:{1, 2}
4. 对称差集(^ / symmetric_difference())
  • 含义:两个集合中互不相同的元素(并集 - 交集)。
  • 语法:a ^ ba.symmetric_difference(b)
print(a ^ b)  # 输出:{1, 2, 5, 6}
print(a.symmetric_difference(b))  # 输出:{1, 2, 5, 6}
5. 集合关系判断(子集/超集/互斥)
关系运算符方法含义
子集a <= ba.issubset(b)A 中所有元素都在 B 中(A 是 B 的子集)
真子集a < b-A 是 B 的子集,且 A ≠ B
超集a >= ba.issuperset(b)B 中所有元素都在 A 中(A 是 B 的超集)
真超集a > b-A 是 B 的超集,且 A ≠ B
互斥(无交集)-a.isdisjoint(b)A 和 B 没有共同元素(交集为空)
a = {1, 2, 3}
b = {1, 2, 3, 4, 5}
c = {6, 7}

# 子集判断
print(a <= b)  # 输出:True(a 所有元素都在 b 中)
print(a.issubset(b))  # 输出:True
print(a < b)  # 输出:True(a 是 b 的真子集)

# 超集判断
print(b >= a)  # 输出:True(b 包含 a 所有元素)
print(b.issuperset(a))  # 输出:True

# 互斥判断
print(a.isdisjoint(c))  # 输出:True(a 和 c 无交集)
print(a.isdisjoint(b))  # 输出:False(a 和 b 有交集)

(五)其他常用操作

1. 集合复制(copy()

创建集合的浅拷贝(新集合,修改新集合不影响原集合):

s = {1, 2, 3}
s_copy = s.copy()
s_copy.add(4)
print(s)  # 输出:{1, 2, 3}(原集合不变)
print(s_copy)  # 输出:{1, 2, 3, 4}(新集合变化)
2. 集合长度(len()

获取集合中元素的个数:

s = {1, 2, 3, "a"}
print(len(s))  # 输出:4
3. 遍历集合(for 循环)

因集合无序,遍历顺序与添加顺序无关:

s = {1, 2, 3, "a"}
for elem in s:
    print(elem)  # 输出顺序不确定(如:1, 'a', 2, 3)

四、集合推导式(快速创建集合)

集合推导式是 Python 中简洁创建集合的方式,语法与列表推导式类似,但用 {} 包裹,自动去重。

语法:{表达式 for 变量 in 可迭代对象 if 条件}

  • 作用:遍历可迭代对象,根据条件筛选元素,通过表达式转换后生成集合(自动去重)。

示例:

# 1. 生成 1-10 的偶数集合
even_set = {x for x in range(1, 11) if x % 2 == 0}
print(even_set)  # 输出:{2, 4, 6, 8, 10}

# 2. 字符串去重并转换为大写
str_set = {c.upper() for c in "abac"}
print(str_set)  # 输出:{'A', 'B', 'C'}

# 3. 列表去重并筛选大于 3 的元素
list1 = [1, 2, 2, 3, 4, 5, 5]
filter_set = {x for x in list1 if x > 3}
print(filter_set)  # 输出:{4, 5}

五、不可变集合(frozenset)

Python 中还有一种不可变集合frozenset),与普通集合(set)的区别是:

  • frozenset 不可修改(不能添加、删除元素);
  • frozenset 可哈希,因此可以作为普通集合的元素或字典的键。

1. 创建 frozenset

语法:frozenset(可迭代对象)

# 创建不可变集合
fs = frozenset({1, 2, 3})
print(fs)  # 输出:frozenset({1, 2, 3})
print(type(fs))  # 输出:<class 'frozenset'>

# 不可修改(以下代码报错)
# fs.add(4)  # AttributeError: 'frozenset' object has no attribute 'add'
# fs.remove(2)  # AttributeError: 'frozenset' object has no attribute 'remove'

2. frozenset 的用途(作为集合元素/字典键)

# 1. frozenset 作为普通集合的元素
s = {frozenset({1, 2}), frozenset({3, 4})}
print(s)  # 输出:{frozenset({1, 2}), frozenset({3, 4})}

# 2. frozenset 作为字典的键
d = {frozenset({1, 2}): "a", frozenset({3, 4}): "b"}
print(d)  # 输出:{frozenset({1, 2}): 'a', frozenset({3, 4}): 'b'}

六、集合的应用场景

结合集合的特性,以下是最常用的实际场景:

1. 快速去重

这是集合最经典的用法,比列表推导式去重更简洁高效:

# 列表去重(转换为集合再转回列表)
list1 = [1, 2, 2, 3, 3, 3]
unique_list = list(set(list1))
print(unique_list)  # 输出:[1, 2, 3](顺序不确定,若需保留顺序可结合字典)

# 保留原列表顺序的去重(Python 3.7+ 字典有序)
unique_list_ordered = list(dict.fromkeys(list1))
print(unique_list_ordered)  # 输出:[1, 2, 3]

2. 元素存在性判断(高效)

适合需要频繁判断“元素是否存在”的场景(如用户权限校验、数据查重):

# 模拟用户权限集合
user_permissions = {"read", "write", "delete"}
# 判断是否有管理员权限
if "admin" in user_permissions:
    print("有管理员权限")
else:
    print("无管理员权限")  # 输出:无管理员权限

3. 数据筛选(交集/并集/差集)

适用于两个数据集的对比分析(如筛选共同用户、合并数据、找出差异数据):

# 示例:筛选两个班级的共同学生
class1 = {"Tom", "Jerry", "Alice"}
class2 = {"Alice", "Bob", "Charlie"}

# 共同学生(交集)
common_students = class1 & class2
print("共同学生:", common_students)  # 输出:{'Alice'}

# 所有学生(并集)
all_students = class1 | class2
print("所有学生:", all_students)  # 输出:{'Tom', 'Jerry', 'Alice', 'Bob', 'Charlie'}

# 仅 class1 有的学生(差集)
only_class1 = class1 - class2
print("仅 class1 有:", only_class1)  # 输出:{'Tom', 'Jerry'}

4. 快速统计不重复元素个数

结合 len()set(),统计可迭代对象中不重复元素的数量:

# 统计字符串中不重复字符的个数
s = "abacabc"
unique_char_count = len(set(s))
print("不重复字符个数:", unique_char_count)  # 输出:3(a、b、c)

七、注意事项与常见坑

  1. 无序性导致的索引问题:集合没有索引,不能用 s[0] 访问元素,也不能切片(如 s[1:3]),只能用 in 判断或遍历。
  2. 元素必须可哈希:列表、字典、集合不能作为集合元素,若需存储多个值,可改用元组(如 (1, 2))。
  3. {}set() 的区别{} 不能创建空集合(创建的是空字典),空集合必须用 set()
  4. 集合运算的对象必须是集合:运算符(&| 等)两边必须是集合,若需对列表/元组运算,需先转换为集合:
    list1 = [1, 2, 3]
    list2 = [3, 4, 5]
    # 错误:列表不能直接用 & 运算
    # print(list1 & list2)  # TypeError
    # 正确:先转集合再运算
    print(set(list1) & set(list2))  # 输出:{3}
    
  5. frozenset 的不可修改性:若需频繁添加/删除元素,用普通 set;若需作为集合元素或字典键,用 frozenset

八、总结

集合是 Python 中高效的“去重+集合运算”工具,核心要点:

  1. 特性:无序、不重复、元素可哈希;
  2. 核心操作:添加(add()/update())、删除(remove()/discard()/pop()/clear())、查找(in)、集合运算(&/|/-/^);
  3. 推导式:{表达式 for 变量 in 可迭代对象 if 条件}(快速创建集合);
  4. 不可变集合:frozenset(不可修改,可作为集合元素/字典键);
  5. 应用场景:去重、元素存在性判断、数据筛选、统计不重复元素个数。

建议多结合实际场景练习(如学生信息管理、数据对比),熟练掌握集合的核心用法,提升代码效率。

更多推荐