Python內建了一個不可變(immutable)集合物件:frozenset,顧名思義,這種集合物件,一旦建立了就不能修改。
概念
下面的程式碼演示了可變和不可變集合使用上的差別:
l = [1, 2, 3, 4]
new_set = set(l)
new_set.remove(1)
print (new_set)
fset = frozenset(l)
fset.remove(1)
如果執行這段程式碼,它的輸出會是:
{2, 3, 4}
Traceback (most recent call last):
File "main.py", line 9, in <module>
fset.remove(1)
AttributeError: 'frozenset' object has no attribute 'remove'
可以看到frozenset例項根本沒有remove方法,當然它也沒有append方法,不能用index給元素賦值。
作用
除了不可修改以外,frozenset所有的用法都和普通set一樣。
這樣一個不可變的集合,有什麼用呢?對效能應該沒有什麼幫助,曾經有文章指出它比普通的set還稍慢一點,主要價值還是體現在可讀性和安全性上:
- 顯式地宣告瞭該集合是不可更改的,給使用者明確提示,一旦誤用了,執行時也會友好報錯;
- 如果想把這個集合,放到另一個set裡面,或者作為dict的key,那它就必須是不可更改的
這第二點,主要是因為dict的key要求是唯一的(或者set內的元素也是唯一的),要是傳一個物件,python會嘗試呼叫它的hash方法生個雜湊串,作為判斷是否唯一的依據,而如果這個物件居然是可變的,它的內容就會影響雜湊串的值。所以在python裡,可變物件根本就沒有內建hash方法,避免它被當做key。
比如下面這段程式碼:
fset = frozenset({4, 5})
s = {1, 2, fset}
print (s)
s = {1, 2, {4, 5}}
首先定義了一個frozenset,把它插入一個普通set;最後一句試圖在普通set裡面,初始化另外一個普通set。輸出結果如下:
{frozenset({4, 5}), 1, 2}
Traceback (most recent call last):
File "main.py", line 27, in <module>
s = {1, 2, {4, 5}}
TypeError: unhashable type: 'set'
可見普通set是不能作為另外一個set的元素的,但frozenset就可以。‘
為什麼Java沒有這個限制
Java程式設計師可能會覺得奇怪,在hashset裡放一個可變物件,不是很正常麼?憑什麼不行啊,我們Java經常這樣寫:
HashSet<ArrayList<String>> masterCollection = new HashSet<ArrayList<String>>();
ArrayList<String> a = new ArrayList<String>();
masterCollection.add(a);
a.add("Hello, World");
for(ArrayList<String> list : masterCollection) {
// do something to list
}
完全不會報錯麼。
So,難道Java的實現機制和Python不一樣?
答案是並沒有,Java只是“容忍”了可能發生的錯誤——這個帖子解釋得很清楚。簡單來說:
Java和Python都是用一個物件的hash值作為key的,相比Python更重視資料一致性,Java更看中靈活性,所以如果我們真的在HashSet裡放了一個可變物件,並且事後改變了它的值,那麼就會發現,HashSet集合,已經檢測不到它的存在了!