2.10. 集合¶
集合(set)是由唯一元素组成的无序集合。添加一个已存在的值不会产生任何效果;迭代时每个值恰好产生一次。当成员判断和去重很重要、而顺序无关紧要时,集合就是合适的工具。
2.10.1. 创建集合¶
对于非空集合使用花括号,对于空集合使用 set():
colours = {"red", "green", "blue"}
empty = set()
花括号看起来像 dict 字面量;单独的 {} 是一个空字典,而不是空集合——这是 Python 的历史遗留问题之一。空集合的情况请使用 set()。
set() 也能从任意可迭代对象构建集合,这是从序列中去除重复项的标准方式:
nums = [1, 2, 2, 3, 1, 4]
unique = set(nums)
print(unique)
输出:
{1, 2, 3, 4}
打印顺序可能不同——集合不保证以任何特定顺序迭代。
2.10.2. 集合与字典对比¶
集合和字典都在哈希表中存储唯一元素。区别在于每个元素随身携带的内容:
两者之间的选择,取决于每个元素旁边的值是否有意义:
当没有值需要伴随每个元素时,选用集合——你只关心元素是否存在,或者你正用并集 / 交集来组合若干组唯一元素。
当每个元素都与某些数据配对、且查找的目的就是取回这些数据时,选用字典——配置映射、缓存、按名字作键的计数器。
这两种类型在表面语法上有很多相似之处,这正是大多数混淆的来源。下面用一段来对比它们的区别:
set |
dict |
|
|---|---|---|
存放 |
唯一元素 |
唯一键,每个键带一个值 |
非空字面量 |
|
|
空字面量 |
|
|
成员测试 |
|
|
取一个值 |
不适用 |
|
添加一个元素 |
|
|
迭代 |
产生元素 |
产生键(用 |
非空字面量与空字面量之间的不对称性是值得特别指出的陷阱:
里面带元素的花括号——
{1, 2, 3}——是集合字面量;带键值对的花括号——{"a": 1}——是字典字面量。解析器靠里面的内容来区分它们。里面什么都没有的花括号——
{}——是空字典,而不是空集合。字典出现得更早;空字面量归它所有。空集合根本没有花括号字面量,必须写成set()。
当一个字典的值从不被读取、只读取它的键时,一种常见的模式是改用集合——这能让意图一目了然,并把未使用的值从内存中削减掉。
2.10.3. 添加和删除¶
set.add()—— 插入一个元素。set.discard()—— 若元素存在则删除它,若不存在则什么也不做。set.remove()—— 删除一个元素;若它不存在则引发KeyError。set.clear()—— 清空集合。
s = {1, 2, 3}
s.add(4)
s.discard(99) # silent: 99 not in s
s.remove(2)
print(s)
输出:
{1, 3, 4}
2.10.4. 成员判断¶
in 运算符测试成员关系。在集合上,无论大小它都大致是常数时间——这是当你只需要询问"这个值是否在里面"时,选择集合而非 list 的主要原因:
if "red" in colours:
print("colour is allowed")
内容相同的 list 每次都会从头开始扫描,这对十个元素没问题,但对一万个元素就很慢了。
2.10.5. 集合运算¶
两个集合可以用常见的数学运算来组合。每种运算都同时具有运算符形式和方法形式:
a | b或a.union(b)—— 任一集合中的所有元素。a & b或a.intersection(b)—— 只有同时出现在两者中的元素。a - b或a.difference(b)—— 在a中但不在b中的元素。a ^ b或a.symmetric_difference(b)—— 在其中一个但不在两者中的元素。
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(a ^ b)
输出:
{1, 2, 3, 4, 5, 6}
{3, 4}
{1, 2}
{1, 2, 5, 6}
运算符形式是只读的;方法形式接受右侧的任意可迭代对象,而不仅仅是另一个集合(a.union([5, 6]))。选用在上下文中读起来更顺的那种。
2.10.6. 什么可以放进集合¶
集合元素必须是可哈希的——与 dict 键的约束相同。int、float、str、bool、bytes 以及 tuple(当其内容本身都可哈希时)都可以。list 和 dict 不行;尝试添加其中之一会引发 TypeError。
2.10.7. frozenset¶
普通的 set 是可变的:每次调用 add / remove / discard 都会就地改变该对象。这种可变性使它失去可哈希的资格,因此集合不能用作 dict 的键,也不能作为另一个集合的成员。
frozenset 是其不可变的对应物。它具有与 set 相同的查找和运算符(in、|、&、-、^),但没有 add / remove 等任何会修改它的方法。由于其内容永远无法改变,frozenset 的哈希值是良定义的——因此它是可哈希的:
primary = frozenset({"red", "green", "blue"})
secondary = frozenset({"yellow", "purple", "orange"})
palettes = {
primary: "RGB",
secondary: "mixed",
}
print(palettes[primary])
输出:
RGB
从任意可迭代对象构造 frozenset——frozenset() 用于空的情况,frozenset(some_set) 用于对现有集合拍一份不可变的快照:
snapshot = frozenset(s) # immutable copy of s
s.add("new") # snapshot does not change
选用它的两个常见理由: