defaultdict : 無條件字典的演進

雖然Python有數不清的外部函式庫,但只要對標準函式庫有足夠的理解,就能在實務中寫出強大的程式碼。在這篇文章中,我們將深入探討collections.defaultdict

通過這篇文章,你將不僅僅了解基本概念,還能明確知道何時、為什麼以及如何使用defaultdict

對於對於第一篇collections系列中的Counter類別感到好奇的人,建議查看之前的文章。 Python標準函式庫 ① - collections.Counter


1. 基本概念: defaultdict是什麼?

Tux with defaultdicts

defaultdict是Python標準函式庫collections模組中的特殊字典(dict)子類別。普通字典在訪問不存在的鍵時會觸發KeyError,但defaultdict允許你指定自動生成預設值的函數(factory function),使代碼變得更簡潔並降低錯誤的可能性。


2. 基本用法

from collections import defaultdict

d = defaultdict(int)
d['apple'] += 1
print(d)  # defaultdict(<class 'int'>, {'apple': 1})

這裡的int()回傳的預設值為0。在訪問不存在的鍵'apple'時,defaultdict自動生成0而沒有觸發KeyError,然後執行+1


3. 各種預設值範例

from collections import defaultdict

# 預設值: 0 (int)
counter = defaultdict(int)
counter['a'] += 1
print(counter)  # defaultdict(<class 'int'>, {'a': 1})

# 預設值: 空列表
group = defaultdict(list)
group['fruit'].append('apple')
group['fruit'].append('banana')
print(group)  # defaultdict(<class 'list'>, {'fruit': ['apple', 'banana']})

# 預設值: 空集合
unique_tags = defaultdict(set)
unique_tags['tags'].add('python')
unique_tags['tags'].add('coding')
print(unique_tags)  # defaultdict(<class 'set'>, {'tags': {'python', 'coding'}})

# 預設值: 自訂初始值
fixed = defaultdict(lambda: 100)
print(fixed['unknown'])  # 100

4. 實際範例

1. 單字頻率計算

words = ['apple', 'banana', 'apple', 'orange', 'banana']
counter = defaultdict(int)

for word in words:
    counter[word] += 1

print(counter)
# defaultdict(<class 'int'>, {'apple': 2, 'banana': 2, 'orange': 1})

👉 Counter vs defaultdict
單字頻率計算更適合使用collections.Counter(),如果需要統計或排名分析,使用Counter是更明智的選擇。但對於如累計計數等簡單累加,用defaultdict(int)也能使用得很簡潔。

2. 按日期分類整理日誌

logs = [
    ('2024-01-01', 'INFO'),
    ('2024-01-01', 'ERROR'),
    ('2024-01-02', 'DEBUG'),
]

grouped = defaultdict(list)
for date, level in logs:
    grouped[date].append(level)

print(grouped)
# defaultdict(<class 'list'>, {'2024-01-01': ['INFO', 'ERROR'], '2024-01-02': ['DEBUG']})

3. 整理去重的標籤

entries = [
    ('post1', 'python'),
    ('post1', 'coding'),
    ('post1', 'python'),
]

tags = defaultdict(set)
for post, tag in entries:
    tags[post].add(tag)

print(tags)
# defaultdict(<class 'set'>, {'post1': {'python', 'coding'}})

5. 注意事項

  • defaultdict會內部保存預設值生成器,故repr()時可能會與普通dict的形式不同。
  • 在進行JSON序列化時可能會有問題,最好先用dict(d)轉換再處理。
  • 只有在使用[]訪問時才會生成預設值,使用get()則不會。
from collections import defaultdict

d = defaultdict(list)
print(d.get('missing'))  # None
print(d['missing'])      # []

6. 什麼時候使用? – defaultdict的三大決定性優勢

defaultdict在經常使用dict + 條件語句模式的場景下,能同時提升可讀性、可維護性、安全性。尤其在以下三種情況下,你會想,'這種情況下無疑適合使用defaultdict!'。

6-1. 無條件地計數/累計的聚合代碼

from collections import defaultdict

# 一般 dict
counts = {}
for item in items:
    if item not in counts:
        counts[item] = 0
    counts[item] += 1

# defaultdict
counts = defaultdict(int)
for item in items:
    counts[item] += 1

✔ 條件語句的消失使代碼變得簡潔,並減少了出錯的機會。
✔ 特別適合於大數據的處理,如日誌分析、單字計數等。

6-2. 當累積列表/集合時取代setdefault

from collections import defaultdict

# 一般 dict
posts = {}
for tag, post in data:
    if tag not in posts:
        posts[tag] = []
    posts[tag].append(post)

# defaultdict
posts = defaultdict(list)
for tag, post in data:
    posts[tag].append(post)

✔ 比setdefault()直觀得多,且在循環內部也更為簡潔。
最佳化資料分組的結構。

6-3. 自動化嵌套字典的初始化

# 一般字典
matrix = {}
if 'x' not in matrix:
    matrix['x'] = {}
matrix['x']['y'] = 10

# defaultdict 嵌套
matrix = defaultdict(lambda: defaultdict(int))  # 每當鍵不存在時自動生成defaultdict(int)
matrix['x']['y'] += 10

✔ 可以輕鬆創建嵌套資料結構,對多維字典操作非常有利
✔ 在數據挖掘、解析、樹結構存儲中有著強大的優勢。

lambda: defaultdict(int)在內部構建上,當鍵不存在時會返回defaultdict(int),從而自動生成嵌套字典。


7. 總結

collections.defaultdict對初學者來說可能僅僅是一個擴展的dict,但越用越能體會到它是一個使得代碼更加清晰、安全的結構工具

  • 可以無需擔心KeyError而安全使用字典。
  • 可以無條件地對數據進行分組和累加。
  • 可以直觀地構建嵌套字典。
# 透過defaultdict穩定且簡潔地處理的範例
salaries = defaultdict(int)
for dept, amount in records:
    salaries[dept] += amount

如果能以一行代碼同時避免錯誤 + 提升可讀性 + 提高可維護性,
那麼defaultdict就不是一個簡單的便利功能,而是符合Python思維方式的核心工具

下次我們將探討pathlib
這是一種以面向對象的方式處理文件/目錄的現代方法,對於從初學者到中級使用者都會有很大的幫助熟悉os.path的開發者會感到"哇!比os.path簡單多了!"期待下篇文章。