iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 3
3
Data Technology

30天python雜談系列 第 3

collections雜談之一 ——— dict的key值存不存在乾我屁事

python collections雜談之一

python提供了我們一些內建的數據結構類型,比如說list,tuple,set,dict,但是當我們在一些比較煩雜的數據處理中,會發現只有這幾種結構類型是不夠用的,至少我們要多花費一些心力,自定義一些類別,在這些內建結構的基礎上多新增一些容易使用的功能,但既然python有如此活躍的開發社群,我們當然不用自己辛苦的重造輪子,python的collections模組,就在這些基礎上多提供了一些簡便好用的額外結構,在這幾天的collection雜談系列,就是要針對這幾種結構的使用情境分享我的一些看法,希望能夠幫助到大家,也請大家不吝指教!

1. defaultdict:

通常在我們取用調用某個dict的key值的時候,都必須事先檢查這個key值是否存在,否則如果直接調用不存在的key值,會直接拋出一個KeyError,比如說:

(1) 統計一個list裡每個元素出現的個數:

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

(2) 建立一個一對多的multidict:

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值給我們使用。

2. Counter:

另外再補充一下關於統計對象個數的議題,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都是非常實用的選擇,不應該自己重造一個輪子來使用。


上一篇
動態型別雜談之二 ——— 所有的變數都是reference
下一篇
collections雜談之二 ——— 看似雞肋的tuple
系列文
30天python雜談30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言