P2F-Python集合完全指南-从创建到去重集合运算的Python编程利器

0 阅读16分钟

P2F-Python集合完全指南-从创建到去重集合运算的Python编程利器

📝 摘要

集合(Set)是 Python 中一种非常实用的数据结构,核心特点是无序且唯一。它最大的用途就是去重,另外还有高效的成员检测和丰富的集合运算(并集、交集、差集等)。本文我们将从零开始,系统学习集合的创建、基本操作、运算方法、去重应用,以及不可变的 frozenset。通过大量代码示例和图表,帮助我们快速掌握集合的各种用法 💪


1. 前置知识点 📚

在开始学习集合之前,我们需要先了解几个基础概念 📖

可迭代对象:可以逐个访问元素的对象,如列表、元组、字符串等。

可变 vs 不可变

  • 可变:创建后可修改(列表、字典、集合)
  • 不可变:创建后不能修改(数字、字符串、元组)

哈希(Hash):快速查找技术。不可变对象有固定哈希值,可变对象不能哈希。

如果这些概念不太懂,可以看看:


2. 什么是集合(Set)📖

2.1 集合的核心特点 🔍

集合这货啊,简单来说就是"无序且不重复"的元素容器 🔩。它有四个核心特点,我们来一个个看:

1. 无序性 🎲

集合里的元素没有固定顺序,不支持下标访问。也就是说,我们不能用 set[0] 这种方式来取元素。那怎么访问呢?只能通过遍历或者直接判断元素是否存在。好处是什么呢?就是我们不需要关心元素的排列顺序,只要关心"这个元素在不在集合里"就行。

s = {'a', 'b', 'c', 'd', 'e', 'f'}
print(s)  # 每次输出顺序可能不同,例如: {'c', 'a', 'f', 'd', 'b', 'e'}

2. 唯一性

这是集合最迷人的特点——自动去重!当我们往集合里添加重复元素时,Python 会自动忽略它。这个特性在实际开发中超级实用,比如我们想找出列表中不重复的元素,直接转成集合就搞定了。

s = {1, 2, 3, 2, 1, 4, 3}
print(s)  # 输出: {1, 2, 3, 4} - 自动去重了!

3. 可变性 🔧

集合本身是可以修改的,我们可以随时添加或删除元素。不过要注意,集合里的元素必须是不可变类型,比如字符串、数字、元组等。列表和字典这种可变类型是不能作为集合元素的,因为我们没办法确定它的哈希值。

那什么是不可变类型呢?简单来说,就是"创建后不能改变"的数据类型。在 Python 中:

  • 可哈希(hashable)的类型:int、float、str、bool、None、tuple(且元组内全是不可变元素)、frozenset
  • 不可哈希(unhashable)的类型:list、dict、set

为什么集合元素必须可哈希呢?因为集合底层用到了哈希表技术,它需要通过元素的哈希值来快速定位元素。如果元素是可以改变的,那它的哈希值就可能变化,集合就乱了套了。所以 Python 直接规定:集合元素必须是不可变类型!

# 这些可以:
s1 = {1, 2, 3}                    # 整数
s2 = {'a', 'b', 'c'}               # 字符串
s3 = {(1, 2), (3, 4)}              # 元组(元素都是不可变的)

# 这些会报错:
s4 = {[1, 2], [3, 4]}              # ❌ TypeError: unhashable type: 'list'
s5 = {{'a': 1}}                    # ❌ TypeError: unhashable type: 'dict'
s6 = {{1, 2}}                      # ❌ TypeError: unhashable type: 'set'

4. 高效操作

集合底层基于哈希表实现,这意味着查找、添加、删除元素的时间复杂度都是 O(1),也就是常数时间。无论集合里有100个元素还是100万个元素,操作速度都差不多。这可比列表的线性查找快多了!


2.2 集合 vs 列表 vs 元组对比 ⚖️

光说集合可能不太好理解,我们来把它和列表、元组放一起对比看看 📊:

特性集合(set)列表(list)元组(tuple)
有序性❌ 无序✅ 有序✅ 有序
唯一性✅ 自动去重❌ 可以重复❌ 可以重复
可变性✅ 可变✅ 可变❌ 不可变
索引访问❌ 不支持✅ 支持✅ 支持
元素类型必须是不可变类型可以是任意类型可以是任意类型
查找效率O(1) 极快O(n) 较慢O(n) 较慢
内存占用较高较低最低

从表格能看出来,集合最适合的场景就是:需要快速查找、需要去重、需要集合运算的时候 🏆。如果你需要保持元素顺序或者通过索引访问,那就用列表;如果你需要存储固定数据、追求极致性能,那就用元组。


3. 集合创建(set creation)✍️

3.1 使用花括号创建 🅰️

这是最直接的方式,直接用花括号把元素括起来,元素之间用逗号分隔:

# 基本创建
fruits = {"apple", "banana", "orange"}
print(fruits)  # {'banana', 'orange', 'apple'}

# 数字集合
numbers = {1, 2, 3, 4, 5}
print(numbers)  # {1, 2, 3, 4, 5}

# 混合类型(只要是不可变类型就行)
mixed = {"hello", 123, (1, 2, 3)}
print(mixed)  # {'hello', 123, (1, 2, 3)}

还有一点很方便——自动去重!如果我们写重复的元素,Python 会自动帮我们去掉:

s = {1, 2, 3, 2, 1, 4, 3}
print(s)  # {1, 2, 3, 4} - 自动去重了!

不过这里有个坑:空的花括号 {} 创建的不是空集合,而是空字典! 要创建空集合必须用 set()(详见 3.2 使用 set() 函数创建)。

# ❌ 这是字典,不是集合!
empty_dict = {}
print(type(empty_dict))  # <class 'dict'>

# ✅ 这才是空集合
empty_set = set()
print(type(empty_set))  # <class 'set'>
print(empty_set)  # set()

3.2 使用 set() 函数创建 🅱️

set() 函数可以创建集合,还有几个常用用法:

1. 创建空集合

empty_set = set()
print(empty_set)  # set()

2. 从列表、元组、字符串转换

这个功能超级实用!我们可以把其他可迭代对象转成集合,自动去重:

# 从列表创建(自动去重)
lst = [1, 2, 3, 2, 1, 4]
s1 = set(lst)
print(s1)  # {1, 2, 3, 4}

# 从元组创建
tup = (1, 2, 3, 2, 1)
s2 = set(tup)
print(s2)  # {1, 2, 3}

# 从字符串创建(把字符拆成集合,自动去重)
text = "hello"
s3 = set(text)
print(s3)  # {'h', 'e', 'l', 'o'} - 注意 'l' 只出现一次

3. 从集合创建(复制)

original = {1, 2, 3}
copied = set(original)
print(copied)  # {1, 2, 3}

3.3 集合推导式创建 🦾

和列表推导式类似,集合也有推导式!语法几乎一样,只是把方括号换成花括号。如果你想了解更多推导式的细节,可以看看 P5D-Python_推导式完全指南 📚:

# 基本语法
squares = {x**2 for x in range(1, 6)}
print(squares)  # {16, 1, 4, 9, 25}

# 带条件筛选
even_squares = {x**2 for x in range(1, 10) if x % 2 == 0}
print(even_squares)  # {16, 4, 36, 64}

# 从字符串过滤
text = "hello world"
unique_vowels = {char for char in text if char in 'aeiou'}
print(unique_vowels)  # {'e', 'o'}

# 复杂一点的条件
pairs = {(x, y) for x in range(3) for y in range(3)}
print(pairs)  # {(0, 0), (0, 1), (0, 2), (1, 0), ...}

集合推导式和列表推导式的区别:

  • 列表推导式用方括号 []
  • 集合推导式用花括号 {}
  • 集合会自动去重,列表不会

3.4 集合创建方式对比 📊

我们来对比一下这三种创建方式:

创建方式适用场景示例
花括号 {}已知具体元素,直观简洁{1, 2, 3}
set() 函数从其他数据转换、创建空集合set([1,2,3])set()
集合推导式批量生成、有筛选逻辑{x**2 for x in range(5)}

使用建议:

  • 如果知道具体元素,用花括号最直观
  • 如果要从列表去重,用 set()
  • 如果要批量生成有规律的元素,用推导式

4. 集合基本操作 🛠️

4.1 访问集合中的元素

我们前面说过,集合是无序的,所以不能用索引来访问元素。那怎么访问呢?两个办法:遍历成员检测 🔍

1. 遍历集合

fruits = {"apple", "banana", "orange"}

# 用 for 循环遍历
for fruit in fruits:
    print(fruit)  # 每次顺序可能不同

2. 成员检测

想知道某个元素在不在集合里?用 in 操作符,超快!

fruits = {"apple", "banana", "orange"}

print("apple" in fruits)    # True
print("grape" in fruits)   # False

这就是集合最强大的地方——成员检测超级快!无论集合里有10个还是100万个元素,in 操作都是 O(1) 复杂度。


4.2 添加元素 ➕

往集合里添加元素有两种方式:

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

s = {1, 2, 3}
s.add(4)
print(s)  # {1, 2, 3, 4}

# 如果添加已存在的元素,什么都不会发生(自动去重)
s.add(2)
print(s)  # {1, 2, 3, 4} - 2 已经在集合里了

2. update() - 添加多个元素

s = {1, 2, 3}
s.update([4, 5, 6])       # 从列表添加
print(s)  # {1, 2, 3, 4, 5, 6}

s.update((7, 8))          # 从元组添加
print(s)  # {1, 2, 3, 4, 5, 6, 7, 8}

s.update({9, 10})         # 从集合添加
print(s)  # {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

s.update("hello")         # 从字符串添加(每个字符)
print(s)  # {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 'h', 'e', 'l', 'o'}

4.3 删除元素 🗑️

删除元素有四种方式,各有特点:

1. remove() - 删除指定元素(元素不存在会报错)

s = {1, 2, 3, 4, 5}
s.remove(3)
print(s)  # {1, 2, 4, 5}

# 如果元素不存在,会抛出 KeyError
s.remove(100)  # KeyError: 100

2. discard() - 删除指定元素(元素不存在不会报错)

s = {1, 2, 3, 4, 5}
s.discard(3)
print(s)  # {1, 2, 4, 5}

# 元素不存在也不会报错,安全感满满!
s.discard(100)  # 什么都不发生,程序继续运行

3. pop() - 随机删除并返回一个元素

s = {1, 2, 3, 4, 5}
removed = s.pop()
print(f"删除的元素: {removed}")  # 随机一个元素
print(f"剩余集合: {s}")

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

4. clear() - 清空集合

s = {1, 2, 3, 4, 5}
s.clear()
print(s)  # set() - 变成空集合了

4.4 计算集合长度

想知道集合里有多少个元素?用 len() 函数:

s = {1, 2, 3, 4, 5}
print(len(s))  # 5

# 自动去重,所以长度会变
s = {1, 1, 1, 2, 2, 3}
print(len(s))  # 3

我们来看个对比表格 🧮:

方法作用特点
add(x)添加单个元素元素已存在则忽略
update(iterable)添加多个元素可接受列表、元组、集合、字符串
remove(x)删除指定元素元素不存在会报错
discard(x)删除指定元素元素不存在不报错
pop()随机删除一个返回被删除的元素,空集合报错
clear()清空集合集合变为空集合

5. 集合运算 ⚔️

5.1 并集(Union)🔗

并集就是把两个集合的所有元素合并在一起,重复的只保留一个。想象一下两个班的学生,合并成一个班 🎉

运算符方式

set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 用 | 运算符
result = set1 | set2
print(result)  # {1, 2, 3, 4, 5}

方法方式

set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 用 union() 方法
result = set1.union(set2)
print(result)  # {1, 2, 3, 4, 5}

# union() 可以接收多个参数
result = set1.union(set2, {6, 7})
print(result)  # {1, 2, 3, 4, 5, 6, 7}

我们来看个图示 🎨:

flowchart TB
    subgraph A[集合A]
        direction LR
        A1["1"]
        A2["2"]
        A3["3"]
    end
    subgraph B[集合B]
        direction LR
        B1["3"]
        B2["4"]
        B3["5"]
    end
    A & B --> |并集| R[结果: 1,2,3,4,5]
    style R fill:#90EE90

5.2 交集(Intersection)🎯

交集就是两个集合中都有的元素。想象一下两个班都参加了某项活动的学生 👥

运算符方式

set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 用 & 运算符
result = set1 & set2
print(result)  # {3}

方法方式

set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 用 intersection() 方法
result = set1.intersection(set2)
print(result)  # {3}

# intersection() 可以接收多个参数
set3 = {3, 5, 6}
result = set1.intersection(set2, set3)
print(result)  # {3}

图示 🎨:

flowchart TB
    subgraph A[集合A]
        direction LR
        A1["1"]
        A2["2"]
        A3["3"]
    end
    subgraph B[集合B]
        direction LR
        B1["3"]
        B2["4"]
        B3["5"]
    end
    A & B --> |交集| R[结果: 3]
    style R fill:#90EE90

5.3 差集(Difference)➖

差集就是 A 集合中有但 B 集合中没有的元素。想象一下参加了A活动但没参加B活动的学生 📋

运算符方式

set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 用 - 运算符
result = set1 - set2
print(result)  # {1, 2}

# 注意顺序:set2 - set1 结果不同
result = set2 - set1
print(result)  # {4, 5}

方法方式

set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 用 difference() 方法
result = set1.difference(set2)
print(result)  # {1, 2}

图示 🎨:

flowchart TB
    subgraph A[集合A: 1,2,3]
        direction LR
        A1["1"]
        A2["2"]
        A3["3"]
    end
    subgraph B[集合B: 3,4,5]
        direction LR
        B1["3"]
        B2["4"]
        B3["5"]
    end
    A -->|减去| B
    B -.->|被移除| B1
    A1 & A2 --> |保留| R[结果: 1,2]
    style R fill:#90EE90

5.4 对称差集(Symmetric Difference)🔄

对称差集就是 A 和 B 的并集减去交集,也就是只属于其中一个集合的元素。想象一下参加了A或B活动(但不同时参加两个)的学生 🤔

运算符方式

set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 用 ^ 运算符
result = set1 ^ set2
print(result)  # {1, 2, 4, 5}

方法方式

set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 用 symmetric_difference() 方法
result = set1.symmetric_difference(set2)
print(result)  # {1, 2, 4, 5}

图示 🎨:

flowchart TB
    subgraph A[集合A]
        direction LR
        A1["1"]
        A2["2"]
        A3["3"]
    end
    subgraph B[集合B]
        direction LR
        B1["3"]
        B2["4"]
        B3["5"]
    end
    A & B --> |对称差集| R[结果: 1,2,4,5]
    style R fill:#90EE90

5.5 子集和超集 📊

子集和超集用来判断集合之间的关系:

  • 子集:A 是 B 的子集,说明 A 的所有元素 B 都有
  • 超集:A 是 B 的超集,说明 A 包含了 B 的所有元素

子集判断

A = {1, 2, 3}
B = {1, 2, 3, 4, 5}

# 用 <= 判断 A 是否是 B 的子集
print(A <= B)           # True
print(A.issubset(B))   # True

# 用 < 判断 A 是否是 B 的真子集(不是相等的情况)
print(A < B)            # True
print(A == B)          # False

超集判断

A = {1, 2, 3, 4, 5}
B = {1, 2, 3}

# 用 >= 判断 A 是否是 B 的超集
print(A >= B)           # True
print(A.issuperset(B)) # True

# 用 > 判断 A 是否是 B 的真超集
print(A > B)            # True
print(A == B)          # False

我们来看个总结表格 📊:

运算运算符方法说明
并集|union()所有元素合并
交集&intersection()共同元素
差集-difference()A有B没有
对称差集^symmetric_difference()仅属于其中一个
子集<=issubset()A全在B里
真子集<-A全在B里且不相等
超集>=issuperset()B全在A里
真超集>-B全在A里且不相等

6. 集合方法 🔧

我们在前面已经学过增删方法(add、remove、discard、pop、clear)和运算方法(union、intersection、difference)。现在我们来聊聊那些原地修改的运算方法,也就是带 _update 后缀的这些方法 🔧

6.1 增删方法

这部分内容我们在 第四章 已经详细介绍过了,这里简单回顾一下:

方法作用
add(x)添加单个元素
update(iterable)添加多个元素
remove(x)删除指定元素,不存在会报错
discard(x)删除指定元素,不存在不报错
pop()随机删除并返回元素
clear()清空集合
copy()复制集合

6.2 原地运算方法

这些方法会直接修改原集合,而不是返回一个新集合。它们的名字都有 _update 后缀 ⚡

1. intersection_update() - 原地求交集

s1 = {1, 2, 3, 4}
s2 = {3, 4, 5, 6}

# 求交集,并原地修改 s1
s1.intersection_update(s2)
print(s1)  # {3, 4}

2. difference_update() - 原地求差集

s1 = {1, 2, 3, 4}
s2 = {3, 4, 5, 6}

# 求差集,并原地修改 s1
s1.difference_update(s2)
print(s1)  # {1, 2}

3. symmetric_difference_update() - 原地求对称差集

s1 = {1, 2, 3, 4}
s2 = {3, 4, 5, 6}

# 求对称差集,并原地修改 s1
s1.symmetric_difference_update(s2)
print(s1)  # {1, 2, 5, 6}

4. update() - 原地求并集

s1 = {1, 2, 3}
s2 = {3, 4, 5}

# 求并集,并原地修改 s1
s1.update(s2)
print(s1)  # {1, 2, 3, 4, 5}

我们来看个对比表格 📊:

方法作用原集合变化
union()返回并集不变
update()原地并集修改原集合
intersection()返回交集不变
intersection_update()原地交集修改原集合
difference()返回差集不变
difference_update()原地差集修改原集合
symmetric_difference()返回对称差集不变
symmetric_difference_update()原地对称差集修改原集合

使用场景:如果我们不需要保留原集合,用原地运算方法可以节省内存;如果需要保留原集合,用返回新集合的方法。


7. 集合去重应用 💡

集合最大的用处就是去重,我们来看看几种常见场景 🧹

7.1 列表去重

lst = [1, 2, 3, 2, 1, 4, 3]
unique = list(set(lst))
print(unique)  # [1, 2, 3, 4] - 无序

# 如果需要保持顺序,用 dict.fromkeys()
# 原理:Python 3.7+ 字典保持插入顺序,我们利用键的唯一性来去重
unique_ordered = list(dict.fromkeys(lst))
print(unique_ordered)  # [1, 2, 3, 4]

7.2 字符串去重

text = "hello world"
unique_chars = set(text)
print(unique_chars)  # {'h', 'e', 'l', 'o', ' ', 'w', 'r', 'd'}

# 按原顺序 - 利用字典键的唯一性和保持顺序的特性
unique_ordered = ''.join(dict.fromkeys(text))
print(unique_ordered)  # helo wrd

7.3 复杂数据去重

对于列表里的复杂数据(字典、元组等),可以用 dict.fromkeys()

data = [{'a': 1}, {'b': 2}, {'a': 1}, {'b': 2}]
unique = list(dict.fromkeys(data))
print(unique)  # [{'a': 1}, {'b': 2}]

8. frozenset:不可变集合 🔐

frozenset 就是"冻住"的集合——不可变的集合。它是 set 的兄弟,但一旦创建就不能修改 🔒

8.1 frozenset 的创建

# 从可迭代对象创建
fs1 = frozenset([1, 2, 3, 4, 5])
print(fs1)  # frozenset({1, 2, 3, 4, 5})

# 从字符串创建
fs2 = frozenset("hello")
print(fs2)  # frozenset({'h', 'e', 'l', 'o'})

# 从集合创建
fs3 = frozenset({1, 2, 3})
print(fs3)  # frozenset({1, 2, 3})

8.2 frozenset 的使用场景

1. 作为字典的键

因为 frozenset 是不可变的,所以它可以哈希,能作为字典的键:

# 用 frozenset 作为字典的键
favorites = {
    frozenset(['apple', 'banana']): '水果',
    frozenset(['chicken', 'beef']): '肉类'
}
print(favorites)  # {frozenset({'apple', 'banana'}): '水果', ...}

2. 作为集合的元素

普通的 set 是不能作为另一个 set 的元素的(因为 set 是可变的),但 frozenset 可以:

# 集合的集合 - 普通 set 不行
set_of_sets = {frozenset([1, 2]), frozenset([3, 4])}
print(set_of_sets)  # {frozenset({1, 2}), frozenset({3, 4})}

3. 需要哈希的时候

frozenset 是可哈希的,普通 set 不行:

fs = frozenset([1, 2, 3])
print(hash(fs))  # 可以哈希

s = {1, 2, 3}
# print(hash(s))  # TypeError: unhashable type: 'set'

我们来看个对比表格 📊:

特性setfrozenset
可变性可变不可变
作为字典键❌ 不行✅ 可以
作为集合元素❌ 不行✅ 可以
哈希❌ 不可哈希✅ 可哈希

9. 总结 🎉

到这里,我们就把 Python 集合的内容都学完了!我们来回顾一下 📚

集合的核心特点:

  • 无序:不能用索引访问
  • 唯一:自动去重
  • 高效:查找、添加、删除都是 O(1)

常用的操作:

  • 创建:{}set()、集合推导式
  • 增删:add、remove、discard、pop、clear
  • 运算:并集、交集、差集、对称差集
  • 判断:子集、超集

特殊类型:

  • frozenset:不可变集合,可以作为字典键或集合元素

集合是 Python 中非常强大的数据结构,特别适合需要去重、快速查找、集合运算的场景。多练习多用,你会发现它的魅力的!💪


最后更新时间:2026-04-10