“集”这个概念在 Python
中算是比较年轻的,同时它的使用率也比较低。set
和它的不可变的姊妹类型 frozenset
直到 Python 2.3
才首次以模块的形式出现,然后在 Python 2.6
中它们升级成为内置类型。
集合的本质是许多唯一
对象的聚集。因此,集合可以用于去重:
l = ['spam', 'spam', 'eggs', 'spam']
set(l)
# {'eggs', 'spam'}
list(set(l))
# ['eggs', 'spam']
集合中的元素必须是可散列的,set
类型本身是不可散列的,但是 frozenset
可以。因此可以创建一个包含不同 frozenset
的 set
。
除了保证唯一性
,集合还实现了很多基础的中缀运算符
。给定两个集合 a
和 b
,a | b
返回的是它们的合集,a & b
得到的是交集,而 a - b
得到的是差集。合理地利用这些操作,不仅能够让代码的行数变少,还能减少 Python
程序的运行时间。这样做同时也是为了让代码更易读,从而更容易判断程序的正确性,因为利用这些运算符可以省去不必要的循环和逻辑操作。
例如,我们有一个电子邮件地址的集合(haystack
),还要维护一个较小的电子邮件地址集合(needles
),然后求出 needles
中有多少地址同时也出现在了 heystack
里。借助集合操作,我们只需要一行代码就可以了。
示例1:
found = len(needles & haystack)
如果不使用交集操作的话,代码可能就变成了下面示例:
示例2:
found = 0
for n in needles:
if n in haystack:
found += 1
上面示例比下面示例的速度要快一些;另一方面,下面示例可以用在任何可迭代对象needles
和 haystack
上,而上面示例则要求两个对象都是集合。话再说回来,就算手头没有集合,我们也可以随时建立集合
示例3:
found = len(set(needles) & set(haystack))
# 或者
found = len(set(needles).intersection(haystack))
示例 3 里的这种写法会牵扯到把对象转化为集合的成本,不过如果 needles
或者是haystack
中任意一个对象已经是集合,那么示例 3 的方案可能就比示例 2 里的要更高效。
以上的所有例子的运行时间都能在 3 毫秒左右,在含有 10 000 000
个元素的 haystack
里搜索 1000
个值,算下来大概是每个元素 3 微秒。
除了速度极快的查找功能(这也得归功于它背后的散列表
),内置的 set
和 frozenset
提供了丰富的功能和操作,不但让创建集合的方式丰富多彩,而且对于 set
来讲,我们还可以对集合里已有的元素进行修改。在讨论这些操作之前,先来看一下相关的句法。
除空集之外,集合的字面量——{1}、{1, 2}
,等等——看起来跟它的数学形式一模一样。如果是空集,那么必须写成 set() 的形式。
注意:
句法的陷阱: 不要忘了,如果要创建一个空集,你必须用不带任何参数的构造方法 set()
。如果只是写成 {}
的形式,跟以前一样,你创建的其实是个空字典
。
像 {1, 2, 3}
这种字面量句法相比于构造方法(set([1, 2, 3])
)要更快且更易读
。后者的速度要慢一些
,因为 Python
必须先从 set
这个名字来查询构造方法
,然后新建一个列表
,最后再把这个列表传入
到构造方法里。但是如果是像 {1, 2, 3}
这样的字面量,Python 会利用一个专门的叫作 BUILD_SET
的字节码来创建集合。
用 dis.dis
(反汇编函数
)来看看两个方法的字节码的不同
dis('{1}')
检查 {1}
字面量背后的字节码BUILD_SET
几乎完成了所有的工作dis('set([1])')
检查 set([1])
的字节码BUILD_SET
:LOAD_NAME
、BUILD_LIST
和 CALL_FUNCTION
由于 Python
里没有针对 frozenset
的特殊字面量句法,我们只能采用构造
方法。Python 3
里 frozenset
的标准字符串表示形式看起来就像构造方法调用一样。来看这段控制台对话:
SIGN
”单词的挑出来,放到一个集合里。下图列出了可变
和不可变
集合所拥有的方法的概况,其中不少是运算符重载的特殊方法。
collections.abc
中,MutableSet
和它的超类的 UML
类图(箭头从子类指向超类,抽象类和抽象方法的名称以斜体显示,其中省略了反向运算符方法)
下表则包含了数学里集合的各种操作在 Python 中所对应的运算符和方法。其中有些运算符和方法会对集合做就地修改(像 &=
、difference_update
,等等),这类操作在纯粹的数学世界里是没有意义的,另外 frozenset
也不会实现这些操作。
集合的数学运算:这些方法或者会生成新集合,或者会在条件允许的情况下就地修改集合
表中的中缀运算符
需要两侧
的被操作对象
都是集合类型
,但是其他的所有方法则只要求所传入的参数是可迭代对象
。例如,想求 4 个聚合类型 a、b、c
和 d
的合集,可以用 a.union(b, c, d)
,这里 a
必须是个 set
,但是 b、c
和 d
则可以是任何类型的可迭代
对象。
下表里列出了返回值是 True
和 False
的方法和运算符。