Python集合(set)存放的是不重复、可哈希的元素,本身无序。它最常干两件事:给序列去重,以及做并集、交集、差集这类集合运算。因为元素要能哈希,列表、字典不能直接丢进集合,需要先转成元组等可哈希类型。
集合长什么样
非空集合可以用大括号字面量写出。打印时花括号里的顺序不必和你书写时一致,这是无序带来的正常现象:
s = {1, 2, 3}
print(s) # {1, 2, 3}
print(type(s)) # <class 'set'>自动去重
同一个值写进去多次,集合里最终只保留一份。把列表先交给 set 再转回 list,是去重时很省事的写法,但转回来之后的次序往往对不上原列表,若必须保序,要另想办法(例如按首次出现顺序自己筛一遍):
s = {1, 2, 2, 3, 3, 3}
print(s) # {1, 2, 3}
nums = [1, 2, 2, 3, 3, 3, 4]
unique = list(set(nums))
print(unique) # [1, 2, 3, 4] 次序可能不同创建集合
从可迭代对象构造时用 set(...),字符串会按字符拆开再去重。空集合只能写 set():在 Python 语言里 {} 已经被空字典占用,这一点和非空时用花括号的写法容易打架,是新手最常写错的地方:
a = {1, 2, 3}
b = set()
c = set("hello")
print(c) # {'h', 'e', 'l', 'o'}
empty = {}
print(type(empty)) # <class 'dict'>
empty2 = set()
print(type(empty2)) # <class 'set'>添加与删除
add 加入单个元素。discard 与 remove 都能删,差别在目标不存在时:前者什么也不做,后者抛 KeyError。pop 会去掉并返回某一个元素,具体是哪一个并不固定,所以不要写依赖弹出顺序的逻辑。clear 则清空整个集合:
s = {1, 2, 3}
s.add(4)
s.discard(2)
s.remove(1)
s.pop()
print(s)
s.clear()遍历集合
for 可以遍历当前所有元素,只是先后顺序每次运行都可能不一样。成员判断用 in,平均情况下很快,这也是集合适合做是否出现过这类检查的原因:
s = {"apple", "banana", "cherry"}
for x in s:
print(x, end=" ")
print()
print("apple" in s) # True集合运算
两个集合之间可以用运算符,也可以用同名方法,结果等价。并集是两边都收进来,交集是公共部分,差集是只在左边出现的元素,对称差则是去掉公共部分后剩下的两侧私有元素:
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b) # 并集
print(a.union(b))
print(a & b) # 交集
print(a.intersection(b))
print(a - b) # 差集:在 a 不在 b
print(a.difference(b))
print(a ^ b) # 对称差
print(a.symmetric_difference(b))
这些运算默认返回新集合,一般不会改写原来的 a、b。若需要原地更新,可以使用带 update、intersection_update 等字样的方法。
集合关系判断
除了算出一个新集合,还可以直接问是不是子集或超集、有没有交集:
a = {1, 2, 3}
b = {1, 2}
print(b.issubset(a)) # True
print(a.issuperset(b)) # True
print(a.isdisjoint({7})) # True,没有交集常见误区
- 空集合必须写
set(),{}表示空字典。 {[1, 2]}会因列表不可哈希而触发TypeError。- 遍历次序不固定,业务判断里不要拿第几个元素当稳定约定。