作者:周秉誼 / Tomofun 資深技術經理
Dict(字典)資料結構是其中一種Python內建型態,應用非常廣,但在使用dict資料結構時,有一個需要特別處理key還沒有被賦值之前,還沒有初始化的情況。本文將分享幾種常見的賦值判斷和初始值設定的做法。
前言
Python是非常容易學習的程式語言,不只可以運用在日常工作的自動化,也可以作為網路服務的後端(Backend)程式,也有豐富的標準函式庫(Python Standard Library)和套件(Package),使得Python成為多種使用情境下都可以使用的程式語言。
dict(字典)資料結構是其中一種Python內建型態(built-in type),它提供了映射(Mapping)的功能,可以將鍵值(Key)對應到各值資料型態。dict的應用非常廣,像是單純存放不同ID對應的資料(Key-value pair)、計算每個字出現的次數(Word Count)、反向索引(Inverted Index)等等,都可以使用dict資料結構。
在使用dict資料結構時,有一個需要特別處理的情況,就是預設值(default value)或初始值(initial value)。當dict中的某一個key還沒有被賦值之前,如果存取了這個key對應的值就會產生KeyError的例外(Exception)。所以通常都需要對每個key是否已經賦值做些判斷,來進行初始值的設定。
本文將分享幾種常見的賦值判斷和初始值設定的做法。
使用if搭配in進行判斷
dict資料結構提供了in語法,可以用key in dict的方式來判斷某個key是不是在這個dict之中,如果key存在,key in dict的結果會是True。因此在取用某個key的值之前,就可以用in語法先判斷是否需要進行初始值的設定。這個方式會在每個存取之前,都先用if條件式配合in語法進行判斷,而Python對於if條件判斷在執行上是比較慢的。
圖1. 使用if搭配in進行判斷
使用try例外處理進行判斷
當存取不存在於dict中的key會產生KeyError的例外,因此也可以利用這個例外來避免if條件判斷。在存取dict的程式外層加上try語法進行例外處理,如果有KeyError的例外發生,代表某個key不存在,此時再進行預設值的設定。這樣就可以避免在每個iteration都進行if條件判斷,而只有尚未初始化的情況會觸發例外處理。但因為try語法進行例外處理在執行上會花費更多時間,這樣的做法比較適合不常發生key不存在的情境下使用。
圖2. 使用try例外處理進行判斷
使用get()函式
dict資料結構除了使用中括號[]來存取key對應的值,也提供了get()函式可以取得某個key對應的值。如dict.get('key0')就會取出dict中key0對應的值,當key0不存在在dict當中時,就會回傳None。而get()函式可以接受第二個參數,作為key0不存在時的預設值,如dict.get('key0', 0)就會回傳0。還有另一個setdefault()函式,跟get()函式十分相似,但當key不存在時,除了會回傳第二個參數設定的預設值之外,也會在dict中加入這個key和該預設值。
圖3. 使用get()函式
使用defaultdict資料結構
如果覺得自己判key存不存在太過麻煩,可以使用Python標準函式庫中的collections函式庫,裡面有個defaultdict資料結構。defaultdict是dict的子類別(subclass),在宣告的時候就要指定一個函式作為default_factory,當使用中括號[]來存取到不存在的key時,就會自動呼叫default_factory函式將對應的值進行初始化。需要特別注意的是使用中括號[]存取時是對應到__getitem__()函式,這時會呼叫default_factory函式進行初始化,但直接使用get()函式是不會呼叫default_factory函式的。
圖4. 使用defaultdict資料結構
結語
在linux上以Python3.8.10進行測試,在key不存在的情況很少發生時,使用defaultdict最快、再來是使用try例外處理、最慢的是使用if及in條件判斷;但在大部份key都不存在的情況下,使用if及in條件判斷最快、再來是使用get()函式、最慢的是使用try例外處理。
在使用Python的dict資料結構時,對還不存在的key進行初始化的處理,是很常碰到的情況,在了解資料的特性、演算法的需求後,選擇適合的方式進行處理,才能讓程式的效能更好,更具有可讀性。