概要(defaultdict は「未登録キーに自動で初期値を入れる」辞書)
collections.defaultdict は、存在しないキーにアクセスした瞬間に「初期値」を自動作成してくれる辞書です。通常の dict では KeyError になる場面でも、defaultdict なら事前チェックなしで c[key].append(…) や c[key] += 1 が書けます。頻度カウント、グループ化、ネスト構造の作成が短く、安全に書けるのが最大の利点です。
from collections import defaultdict
# 出現回数のカウント(事前チェック不要)
cnt = defaultdict(int)
for w in ["coffee", "tea", "coffee"]:
cnt[w] += 1
print(cnt) # defaultdict(<class 'int'>, {'coffee': 2, 'tea': 1})
Python基本の使い方(ここが重要)
default_factory に「初期値を作る関数」を渡す
defaultdict の第1引数 default_factory は「未登録キーに対してどんな初期値を作るか」を決めます。int は 0、list は []、set は set() を返します。未登録キーにアクセスした瞬間、辞書に新しいキーが作られ、その値が初期化されます。
from collections import defaultdict
# リストでグループ化
groups = defaultdict(list)
for user, team in [("hanako", "A"), ("taro", "B"), ("mika", "A")]:
groups[team].append(user)
print(groups) # {'A': ['hanako', 'mika'], 'B': ['taro']}
Python直接加算・直接 append がそのまま書ける
存在確認や初期化の if を省けます。dict では必要な「キーの有無チェック」が、defaultdict なら不要です。
from collections import defaultdict
# 集計
scores = defaultdict(int)
scores["coffee"] += 3 # 未登録 → 0 を作ってから +3
scores["tea"] += 1
print(scores["sugar"]) # 未登録 → 0 が返る(ついでにキーもできる)
Pythondefault_factory の深掘り(柔軟な初期化)
組み込み型(int, list, set, dict)と関数・lambda
よく使うのは int、list、set。さらに「関数」や「lambda」を渡すと、複雑な初期値も生成できます。
from collections import defaultdict
# 集計+重複排除
by_ext = defaultdict(set)
by_ext[".csv"].add("data1.csv")
by_ext[".csv"].add("data1.csv") # set なので重複しない
# 独自初期値(辞書を入れ子に)
def make_inner():
return {"sum": 0, "count": 0}
stats = defaultdict(make_inner)
stats["coffee"]["sum"] += 120
stats["coffee"]["count"] += 2
Pythonネストした defaultdict で入れ子構造を簡潔に作る
入れ子の構造はネスト defaultdict が便利です。深さごとの初期化を自動化できます。
from collections import defaultdict
Tree = lambda: defaultdict(Tree)
root = Tree()
root["A"]["B"]["C"] = 1
print(root) # 未登録の枝が自動生成される
Pythondict.get と比較して理解する(設計の違い)
dict.get は「値を返すだけ」、defaultdict は「キーを追加する」
dict.get(key, default) は値を返すだけで、辞書にはキーが追加されません。defaultdict はアクセス時にキーと初期値が実体として追加されるため、その後の更新が自然に書けます。カウントやグループ化なら defaultdict が簡潔で安全です。
# dict.get との違い(get は辞書に追加しない)
d = {}
x = d.get("coffee", 0) # 0 を返すだけ
# d["coffee"] は依然として未登録
# defaultdict ならアクセス時にキーができる
from collections import defaultdict
dd = defaultdict(int)
print(dd["coffee"]) # 0 を返し、辞書に 'coffee': 0 が追加される
Python実務での使いどころ(重要ポイントの深掘り)
頻度カウント・グループ化・多段集計を最短で書く
カウントは int、グループ化は list、重複排除は set。多段集計はネスト。defaultdict を使えば「事前初期化の定型」が消え、ロジックが前面に出ます。
from collections import defaultdict
# 1: 頻度カウント
freq = defaultdict(int)
for word in "coffee tea tea sugar coffee coffee".split():
freq[word] += 1
# 2: グループ化
group = defaultdict(list)
for user, team in [("hanako", "A"), ("taro", "B"), ("mika", "A")]:
group[team].append(user)
# 3: 重複排除のグループ化
uniq = defaultdict(set)
for file in ["a.csv", "b.csv", "a.csv"]:
uniq["csv"].add(file)
Python既存 dict に戻したいときは dict() に渡す
外部APIや他ライブラリに渡す際に「普通の dict が欲しい」なら、dict(defaultdict_obj) で変換します。中身はそのままです。
from collections import defaultdict
g = defaultdict(list)
g["A"].append("hanako")
plain = dict(g) # 普通の dict に変換
Python注意点(ミュータブルの共有・メモリ・None不可)
ミュータブルな初期値は「キーごとに別インスタンス」
list や dict を初期値にするとき、各キーで別々のインスタンスが生成されます(安全)。ただし「一つのインスタンスを全キーで使い回す」ような設計は誤りになりがちなので避けます。
from collections import defaultdict
lists = defaultdict(list)
lists["A"].append(1)
lists["B"].append(2)
# A と B は別々のリスト(混ざらない)
Pythondefault_factory=None は禁止(KeyError が発生)
defaultdict を「普通の dict のように」使いたいなら、そもそも dict を使いましょう。default_factory を None にすると、未登録キーで KeyError が出ます。
from collections import defaultdict
# defaultdict(None) は未登録キーで初期化しない → KeyError
d = defaultdict(None)
# d["x"] # KeyError
Python無制限なキー追加に注意(メモリが増える)
アクセスするだけでキーが増えるため、意図せぬ入力(タイポなど)で辞書が肥大化することがあります。入力値のホワイトリストや検証を組み合わせると安全です。
例題で身につける(定番から一歩先まで)
例題1:CSV の特定列でグループ化して整形出力
import csv
from collections import defaultdict
def group_by_col(path: str, col: int) -> dict[str, list[list[str]]]:
groups = defaultdict(list)
with open(path, "r", encoding="utf-8", newline="") as f:
for row in csv.reader(f):
if len(row) > col:
groups[row[col]].append(row)
return dict(groups) # 普通の dict にして返す
# 使い方例
# print(group_by_col("sales.csv", 0))
Python例題2:拡張子ごとにファイル名を集める(重複排除)
from pathlib import Path
from collections import defaultdict
def files_by_ext(root: str) -> dict[str, set[str]]:
by_ext = defaultdict(set)
for p in Path(root).rglob("*"):
if p.is_file():
by_ext[p.suffix.lower()].add(p.name)
return dict(by_ext)
# print(files_by_ext("downloads"))
Python例題3:2段階の集計(カテゴリ別に数量合計)
from collections import defaultdict
def sum_by_category(rows: list[tuple[str, str, int]]) -> dict[str, dict[str, int]]:
total = defaultdict(lambda: defaultdict(int))
for cat, name, qty in rows:
total[cat][name] += qty
return {cat: dict(names) for cat, names in total.items()}
data = [("drink", "coffee", 2), ("drink", "tea", 1), ("snack", "cookie", 3)]
# print(sum_by_category(data))
Python例題4:正規表現で単語抽出し、先頭文字でグループ化
import re
from collections import defaultdict
def group_by_initial(text: str) -> dict[str, list[str]]:
groups = defaultdict(list)
for w in re.findall(r"[A-Za-z]+", text):
groups[w[0].lower()].append(w.lower())
return dict(groups)
# print(group_by_initial("Coffee tea Sugar syrup"))
Pythonまとめ
defaultdict は「未登録キーの初期化を自動化」することで、カウント、グループ化、ネスト構造の作成を圧倒的に短く、安全にします。default_factory に int・list・set・関数を渡して用途に合わせた初期値を用意し、c[key] += 1 や c[key].append(x) を事前チェックなしで書く。dict.get との違いは「アクセス時にキーが実体として追加される」点で、集計系では特に強みが出ます。ミュータブル初期値はキーごとに別インスタンス、default_factory=None は避ける、入力検証で無制限追加を防ぐ。この基本線を身につければ、初心者でも“辞書まわりの定型”を捨て、ロジックに集中した美しいコードが書けます。
