python提供了我們一些內建的數據結構類型,比如說list,tuple,set,dict,但是當我們在一些比較煩雜的數據處理中,會發現只有這幾種結構類型是不夠用的,至少我們要多花費一些心力,自定義一些類別,在這些內建結構的基礎上多新增一些容易使用的功能,但既然python有如此活躍的開發社群,我們當然不用自己辛苦的重造輪子,python的collections模組,就在這些基礎上多提供了一些簡便好用的額外結構,在這幾天的collection雜談系列,就是要針對這幾種結構的使用情境分享我的一些看法,希望能夠幫助到大家,也請大家不吝指教!
通常在我們取用調用某個dict的key值的時候,都必須事先檢查這個key值是否存在,否則如果直接調用不存在的key值,會直接拋出一個KeyError,比如說:
a_list = ['a','b','x','a','a','b','z']
counter_dict = {}
for element in a_list:
if element not in counter_dict:
counter_dict[element] = 1
else:
counter_dict[element] += 1
key_values = [('even',2),('odd',1),('even',8),('odd',3),('float',2.4),('odd',7)]
multi_dict = {}
for key,value in key_values:
if key not in multi_dict:
multi_dict[key] = [value]
else:
multi_dict[key].append(value)
這種檢查跟我們要執行的功能完全無關,只是為了應付python內部結構一些無法支援我們的一些特性,寫多了會讓我們覺得非常煩瑣,若是過於逆來順受,把這種類型檢查變成一種習慣,這實在是太孬了,而且完全對不起python力求簡潔的哲學。
就如同標題說的,dict的key值存不存在乾我屁事,為何dict不會聰明一點,沒有key值就先生一個default值出來給我,這樣方便多了,所幸對於這種調用key值的議題,collections提供的defaultdict給我們一個很好的解決方案,顧名思義,defaultdict對於我們調用一個不存在的key值,他會先建立一個default值給我們,而這個default值必須由一個可呼叫的函數產生,在我們初始化一個defaultdict時,必須先指定一個產生default值的函數:
from collections import defaultdict
better_dict = defaultdict(list) # default值以一個list()方法產生
check_default = better_dict['a']
print(check_default) # 會輸出list()方法產生的空串列[]
better_dict['b'].append(1) # [1]
better_dict['b'].append(2) # [1,2]
better_dict['b'].append(3) # [1,2,3]
print(better_dict['b'])
因此若想要建立一個multidict,只要用defaultdict(list)就可以很輕鬆的達成,不需要事先檢查key值存不存在,進而提高程式的可讀性:
from collections import defaultdict
multi_dict = defaultdict(list)
key_values = [('even',2),('odd',1),('even',8),('odd',3),('float',2.4),('odd',7)]
for key,value in key_values:
multi_dict[key].append(value)
print(multi_dict) # 會輸出defaultdict(<class 'list'>, {'float': [2.4], 'even': [2, 8], 'odd': [1, 3, 7]})
但若我們想要直接給予一個固定的值給defaultdict是不行的,會產生TypeError的例外,比如說是在統計元素個數的狀況下,我想要讓default值為0,那有一個方法就是建構一個生成0的函數:
from collections import defaultdict
def zero():
return 0
counter_dict = defaultdict(zero) # default值以一個zero()方法產生
a_list = ['a','b','x','a','a','b','z']
for element in a_list:
counter_dict[element] += 1
print(counter_dict) # 會輸出defaultdict(<function zero at 0x7fe488cb7bf8>, {'x': 1, 'z': 1, 'a': 3, 'b': 2})
然後因為這個defaultdict是dict的一個子類別,也就是說他繼承了dict的所有方法,或是現在這個defaultdict是dict的擴充,一般用在dict的使用方法在defaultdict也可以使用。
而在https://docs.python.org/3.5/library/collections.html#collections.defaultdict 中,有寫明defaultdict的一些擴充特性,也就是__missing__(key)以及default_factory,稍微簡單說明一下:
(1) default_factory:就是defaultdict在初始化的過程中,第一個參數所接受的函數對象(也就是上述的list()或是zero()),而第二個之後的參數都比照一般dict傳入參數的格式。
(2) __missing__(key):在我們調用不存在的key值時defaultdict會調用__missing__(key)方法,這個方法會利用default_factory創造一個default值給我們使用。
另外再補充一下關於統計對象個數的議題,collections還有一個很好用的數據結構Counter,Counter也是dict的一個子類別,而因為這個數據結構是用於計數中,因此他會比defaultdict多出許多關於計數的方法與屬性,以下先來觀察其使用方法:
from collections import Counter
a_str = 'abcaaabccabaddeae'
counter = Counter(a_str) # 可直接由初始化的方式統計個數
print(counter)
print(counter.most_common(3)) # 輸出最常出現的3個元素
print(counter['a'])
print(counter['z']) # 對於不存在的key值給出default值0
counter.update('aaeebbc') # 可用update的方式繼續統計個數
print(counter)
比起defaultdict,Counter顯然方便太多了,可以用初始化或是update的方式統計,而不用動用到for迴圈,most_common方法還能列出前n個個數最多的元素,順帶一提,初始化統計的方法除了利用字串,也能夠利用list,dict,或是具名參數來統計:
counter = Counter(['a','a','n'])
counter = Counter({'a':2,'n':1})
counter = Counter(a=2,n=1) # 這3個初始化後的結果是相同的
這裡就只稍微介紹一些好用的特性,另外不同Counter之間還能用一般的運算符(+,-,|,&)結合起來統計,詳細運算以及其他方法可以從https://docs.python.org/3.5/library/collections.html#collections.Counter 官方文檔中查閱,一般用python來進行表格統計工作時(比如說讀取csv檔並做計算),Counter都是非常實用的選擇,不應該自己重造一個輪子來使用。