Python 課程 – 資料結構:字典(dict)之 defaultdict 與 Counter
簡介
在 Python 中,dict 是最常用的資料結構之一,提供了「鍵 → 值」的映射關係。然而,當我們需要在字典裡自動產生預設值或進行計數時,原生的 dict 常會讓程式碼變得冗長且易錯。為了簡化這類需求,collections 模組提供了兩個非常實用的子類別:defaultdict 與 Counter。
defaultdict讓你在存取不存在的鍵時,自動以指定的「預設工廠」產生一個初始值,省去手動檢查與if判斷的步驟。Counter則是專為「計數」設計的字典子類別,內建多種統計方法(如most_common、加減運算等),在文字分析、日誌統計、購物車等情境中非常方便。
掌握這兩個工具,不僅能讓程式碼更簡潔、可讀,還能提升開發效率與錯誤防護能力。以下將深入說明它們的概念、使用方式與實務應用。
核心概念
1️⃣ defaultdict:自動產生預設值的字典
defaultdict 繼承自內建的 dict,唯一的差別在於它接受一個 default_factory(預設工廠)作為建構子參數。當存取一個不存在的鍵時,defaultdict 會呼叫 default_factory 產生一個新值,並自動插入字典。
常見的 default_factory
| 類型 | 說明 | 產生的預設值 |
|---|---|---|
list |
用於收集多個元素 | [] |
set |
用於去除重複元素 | set() |
int |
用於計數 | 0 |
float |
用於浮點數累計 | 0.0 |
lambda: <value> |
自訂任意預設值 | <value> |
範例 1:分組(groupby)資料
from collections import defaultdict
# 假設有一串座標資料,每個座標都有屬於哪個城市
points = [
("Taipei", (25.03, 121.56)),
("Kaohsiung", (22.63, 120.30)),
("Taipei", (25.04, 121.57)),
("Tainan", (22.99, 120.20)),
]
city_points = defaultdict(list) # default_factory 為 list
for city, coord in points:
city_points[city].append(coord)
print(city_points)
# Output: defaultdict(<class 'list'>, {'Taipei': [(25.03, 121.56), (25.04, 121.57)], 'Kaohsiung': [(22.63, 120.30)], 'Tainan': [(22.99, 120.20)]})
重點:不需要先檢查
city是否已存在於字典,defaultdict會自動建立空列表。
範例 2:統計字元出現次數(不使用 Counter)
from collections import defaultdict
text = "Python程式設計教學"
char_cnt = defaultdict(int) # default_factory 為 int,預設值 0
for ch in text:
char_cnt[ch] += 1
print(char_cnt)
# Output: defaultdict(<class 'int'>, {'P': 1, 'y': 1, 't': 1, 'h': 1, 'o': 1, 'n': 1, '程': 1, '式': 1, '設': 1, '計': 1, '教': 1, '學': 1})
技巧:若只需要簡易計數,
defaultdict(int)已足夠,且執行速度與Counter相當。
範例 3:集合去重的分組
from collections import defaultdict
data = [
("apple", "red"),
("banana", "yellow"),
("apple", "green"),
("banana", "green"),
]
color_set = defaultdict(set) # default_factory 為 set
for fruit, color in data:
color_set[fruit].add(color)
print(color_set)
# Output: defaultdict(<class 'set'>, {'apple': {'red', 'green'}, 'banana': {'yellow', 'green'}})
說明:利用
set作為預設值,自動去除同一水果的重複顏色。
2️⃣ Counter:專用於計數的字典
Counter 繼承自 dict,內部的值皆為整數,代表「某個項目出現的次數」。它提供了許多便利的方法,例如 most_common、elements、加減運算等。
範例 4:文字頻率統計
from collections import Counter
sentence = "Python 是一門簡潔且強大的程式語言。Python 讓開發者能快速完成任務。"
# 先把句子切成單字(簡化示範,實務上會使用 jieba 等斷詞工具)
words = [w for w in sentence if w.isalpha()] # 只保留中文或英文字母
counter = Counter(words)
print(counter)
# Output: Counter({'P': 2, 'y': 2, 't': 2, 'h': 2, 'o': 2, 'n': 2, '是': 1, '一': 1, '門': 1, '簡': 1, '潔': 1, '且': 1, '強': 1, '大': 1, '的': 1, '程': 1, '式': 1, '語': 1, '言': 1, '讓': 1, '開': 1, '發': 1, '者': 1, '能': 1, '快': 1, '速': 1, '完': 1, '成': 1, '任': 1, '務': 1})
重點:
Counter會自動把不存在的鍵視為 0,等同於defaultdict(int),但提供更多統計功能。
範例 5:取得前 N 名高頻詞
top3 = counter.most_common(3)
print(top3) # [('P', 2), ('y', 2), ('t', 2)]
說明:
most_common(n)直接回傳前n個出現次數最高的項目,省去自行排序的麻煩。
範例 6:兩個 Counter 的加法與減法
c1 = Counter(a=3, b=1, c=2)
c2 = Counter(a=1, b=2, d=4)
print(c1 + c2) # Counter({'a': 4, 'c': 2, 'b': 3, 'd': 4})
print(c1 - c2) # Counter({'a': 2, 'c': 2}) # 只保留正值
技巧:使用
+、-、&(交集)與|(聯集)可以快速完成集合運算,對於統計多來源資料特別有用。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記傳入 default_factory |
defaultdict() 若未提供 default_factory,會變成普通 dict,存取不存在的鍵仍會拋 KeyError。 |
務必在建構時明確指定(如 defaultdict(list))。 |
default_factory 回傳可變物件時的共享問題 |
若使用 default_factory=lambda: [],每次呼叫都會產生新列表;但如果使用 default_factory=list,行為相同。若自行寫函式返回同一個物件,可能會導致多個鍵共享同一個列表。 |
使用內建型別或 lambda 產生新實例,避免返回同一個可變物件。 |
Counter 負值的處理 |
減法後的負值會被保留在 Counter 內,但在大多數統計情境下不需要負值。 | 使用 c.subtract(other) 後,+ 或 & 重新正規化,或手動過濾 if v > 0。 |
| 大型資料集的記憶體使用 | defaultdict 與 Counter 都會將每個鍵都保留下來,若鍵的種類非常多,可能會佔用大量記憶體。 |
使用 collections.defaultdict 搭配 itertools.islice 或 外部儲存(如 SQLite、Redis)分批處理。 |
**most_common 產生的列表是 **快照 |
most_common 會返回一個全新列表,對原始 Counter 沒有影響。若需要即時更新,請直接操作 Counter 本身。 |
如需即時排序,可使用 sorted(counter.items(), key=lambda x: x[1], reverse=True)。 |
最佳實踐
- 優先使用
defaultdict或Counter替代手動if key in dict判斷,可提升可讀性。 - 在需要多層嵌套的分組時,考慮使用
defaultdict(lambda: defaultdict(list)),簡化深層結構的建立。 - 對於計數需求,若只需簡單統計,
Counter是最直接的選擇;若需要自訂預設值或更複雜的資料結構,則使用defaultdict。 - 在多執行緒環境下,
defaultdict與Counter本身不是線程安全的,若共享寫入,請使用threading.Lock包裝。
實際應用場景
文字分析與自然語言處理
- 使用
Counter統計詞頻、字頻,快速找出關鍵詞或建立詞雲。 - 結合
defaultdict(list)分組同義詞或同類型詞彙。
- 使用
日誌(log)統計
defaultdict(int)逐行讀取伺服器日誌,統計每個狀態碼(200、404、500)出現次數。Counter可以直接累加多台機器的日誌結果,使用加法合併。
購物車與庫存管理
Counter代表商品數量,支援加減操作,輕鬆處理加購、退貨。defaultdict(set)用於記錄每個客戶購買過的商品集合,避免重複推薦。
資料分組與聚合
- 在 ETL 流程中,利用
defaultdict(list)把同一類別的記錄聚集在一起,再一次性寫入資料庫。 defaultdict(lambda: defaultdict(int))可同時完成「類別 → 子類別 → 數量」的多層聚合。
- 在 ETL 流程中,利用
圖形演算法
- 用
defaultdict(list)建立鄰接表(adjacency list),方便 BFS、DFS 的實作。 Counter追蹤圖中節點的訪問次數或邊的權重累計。
- 用
總結
defaultdict 與 Counter 是 Python collections 模組中兩把「提效神器」:
defaultdict:讓字典在缺少鍵時自動產生預設值,特別適合分組、聚合、集合類型的操作。Counter:專為計數設計,內建排序、加減與集合運算,讓文字統計、日誌分析等任務變得一鍵搞定。
掌握它們的使用方式、注意常見陷阱,並配合最佳實踐,就能在日常開發與資料處理工作中寫出更簡潔、可讀、高效的程式碼。希望這篇文章能幫助你在 Python 的字典世界裡,快速上手 defaultdict 與 Counter,並在實務上發揮最大的威力!祝你編程順利 🚀。