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. 什麼能放進集合¶
集合的元素必須是可雜湊的(hashable)——與 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
選用它有兩個常見的理由: