Python defaultdict:デフォルト値で辞書操作をシンプルにする
Updated on
すべてのPython開発者がこの壁にぶつかったことがあるでしょう:辞書を使って要素をグループ化またはカウントするきれいなループを書き、コードを実行すると、キーがまだ存在しないためKeyErrorでスクリプト全体がクラッシュします。標準的な回避策は、あちこちにif key in dictチェックやtry/except KeyErrorブロックを散りばめることです。10行のデータをグループ化するロジックが、突然20行の防御的なボイラープレートコードに膨れ上がります。
これはスケールすると悪化します。グラフの隣接リストの構築、ログデータの集約、数百万のレコードにわたる単語頻度のカウントなどを行う場合、それらのガード句は積み重なっていきます。開発者としての作業を遅くし、コードレビューを困難にし、ある分岐でチェックを忘れると微妙なバグを引き起こします。
Pythonのcollections.defaultdictは、この問題カテゴリ全体を解消します。これは、欠落している値を自動的に提供するためにファクトリ関数を呼び出す辞書のサブクラスです。KeyErrorもガード句もボイラープレートももう不要です。
defaultdictとは?
defaultdictはPythonの組み込みdictのサブクラスです。主な違い:存在しないキーにアクセスした場合、defaultdictはKeyErrorを発生させる代わりに、デフォルト値で自動的にキーを作成します。
from collections import defaultdict
# 通常のdictはKeyErrorを発生させる
regular = {}
# regular['missing'] # KeyError: 'missing'
# defaultdictは値を自動的に作成する
dd = defaultdict(int)
dd['missing'] # 0を返し、'missing'がキーになる
print(dd) # defaultdict(<class 'int'>, {'missing': 0})コンストラクタは最初の引数としてファクトリ関数を取ります。一般的なファクトリ:
int--0を返すlist--[]を返すset--set()を返すstr--""を返すlambda: value-- 任意のカスタムデフォルト値を返す
defaultdict(int) -- カウントパターン
最も一般的な使い方です。新しいキーはすべて0から始まるので、すぐにインクリメントできます。
from collections import defaultdict
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
# defaultdictなし
counts_regular = {}
for word in words:
if word in counts_regular:
counts_regular[word] += 1
else:
counts_regular[word] = 1
# defaultdict(int)を使用 -- クリーンで直接的
counts = defaultdict(int)
for word in words:
counts[word] += 1
print(dict(counts))
# {'apple': 3, 'banana': 2, 'cherry': 1}defaultdict(list) -- グループ化パターン
関連する要素をまとめます。新しいキーはそれぞれ空のリストから始まります。
from collections import defaultdict
students = [
('Math', 'Alice'),
('Science', 'Bob'),
('Math', 'Charlie'),
('Science', 'Diana'),
('Math', 'Eve'),
('History', 'Frank'),
]
groups = defaultdict(list)
for subject, student in students:
groups[subject].append(student)
for subject, names in groups.items():
print(f"{subject}: {', '.join(names)}")
# Math: Alice, Charlie, Eve
# Science: Bob, Diana
# History: Frank複数のフィールドでレコードをグループ化
from collections import defaultdict
sales = [
{'region': 'East', 'product': 'Widget', 'amount': 100},
{'region': 'West', 'product': 'Gadget', 'amount': 200},
{'region': 'East', 'product': 'Widget', 'amount': 150},
{'region': 'West', 'product': 'Widget', 'amount': 300},
]
by_region_product = defaultdict(list)
for sale in sales:
key = (sale['region'], sale['product'])
by_region_product[key].append(sale['amount'])
for (region, product), amounts in by_region_product.items():
total = sum(amounts)
print(f"{region} - {product}: {amounts} (total: {total})")defaultdict(set) -- ユニークなグループ化
キーごとにユニークな値を自動的に収集します。
from collections import defaultdict
edges = [
('Alice', 'Bob'), ('Alice', 'Charlie'),
('Bob', 'Alice'), ('Bob', 'Diana'),
('Alice', 'Bob'), # duplicate
]
connections = defaultdict(set)
for person, friend in edges:
connections[person].add(friend)
for person, friends in connections.items():
print(f"{person} is connected to: {friends}")
# Alice is connected to: {'Bob', 'Charlie'}
# Bob is connected to: {'Alice', 'Diana'}defaultdict(lambda: value) -- カスタムデフォルト
組み込み型が適合しない場合、lambdaを使って任意のデフォルト値を返します。
from collections import defaultdict
# 欠落エントリのデフォルト値を'N/A'に設定
status = defaultdict(lambda: 'N/A')
status['server1'] = 'running'
status['server2'] = 'stopped'
print(status['server3']) # N/A
# デフォルトの開始残高
accounts = defaultdict(lambda: 100.0)
accounts['alice'] += 50
accounts['bob'] -= 30
print(dict(accounts)) # {'alice': 150.0, 'bob': 70.0}構造化された値を持つデフォルト辞書
from collections import defaultdict
def default_profile():
return {'score': 0, 'level': 1, 'items': []}
profiles = defaultdict(default_profile)
profiles['player1']['score'] += 100
profiles['player1']['items'].append('sword')
profiles['player2']['level'] = 5
print(profiles['player1'])
# {'score': 100, 'level': 1, 'items': ['sword']}
print(profiles['player3'])
# {'score': 0, 'level': 1, 'items': []}ネストされたdefaultdict -- ツリー構造
最も強力なパターンの1つは、defaultdictを再帰的に使用して自動生成辞書を作成することです。
from collections import defaultdict
def tree():
return defaultdict(tree)
taxonomy = tree()
taxonomy['Animal']['Mammal']['Dog'] = 'Canis lupus familiaris'
taxonomy['Animal']['Mammal']['Cat'] = 'Felis catus'
taxonomy['Animal']['Bird']['Eagle'] = 'Aquila chrysaetos'
taxonomy['Plant']['Tree']['Oak'] = 'Quercus'
print(taxonomy['Animal']['Mammal']['Dog']) # Canis lupus familiaris多段階集計
from collections import defaultdict
sales_data = [
(2025, 'Q1', 'Widget', 500),
(2025, 'Q1', 'Gadget', 300),
(2025, 'Q2', 'Widget', 700),
(2026, 'Q1', 'Widget', 600),
]
report = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
for year, quarter, product, amount in sales_data:
report[year][quarter][product] += amount
print(report[2025]['Q1']['Widget']) # 500
print(report[2026]['Q1']['Widget']) # 600defaultdict vs dict.setdefault() vs get() -- 比較
| 機能 | defaultdict | dict.setdefault() | dict.get() |
|---|---|---|---|
| インポート必要 | はい(collections) | いいえ | いいえ |
| キーを自動作成 | はい | はい | いいえ |
| アクセス時にdictを変更 | はい | はい | いいえ |
| 呼び出しごとのカスタムデフォルト | いいえ(グローバルファクトリ) | はい | はい |
| パフォーマンス(繰り返し) | 最速 | 低速(メソッド呼び出しオーバーヘッド) | 最速(ミューテーションなし) |
| 最適な用途 | 繰り返しの蓄積 | 一回限りのデフォルト | 読み取り専用フォールバック |
それぞれの使い分け:
defaultdict:多くのイテレーションで値を構築する場合(カウント、グループ化)dict.setdefault():特定のキーに対して時々デフォルトが必要な場合dict.get():辞書を変更せずにフォールバック付きで値を読み取る場合
defaultdictを通常のdictに変換する
from collections import defaultdict
import json
def defaultdict_to_dict(d):
"""Recursively convert defaultdict to regular dict."""
if isinstance(d, defaultdict):
d = {k: defaultdict_to_dict(v) for k, v in d.items()}
return d
nested = defaultdict(lambda: defaultdict(int))
nested['x']['y'] = 10
nested['a']['b'] = 20
regular = defaultdict_to_dict(nested)
print(json.dumps(regular)) # {"x": {"y": 10}, "a": {"b": 20}}デフォルトファクトリをNoneに設定して無効にすることもできます:
dd = defaultdict(int)
dd['a'] += 1
dd.default_factory = None
# dd['missing'] # これでKeyErrorが発生する実践的な例
グラフの隣接リスト
from collections import defaultdict, deque
edges = [('A', 'B'), ('A', 'C'), ('B', 'D'), ('C', 'D'), ('D', 'E')]
graph = defaultdict(list)
for src, dst in edges:
graph[src].append(dst)
graph[dst].append(src) # undirected graph
def bfs(graph, start):
visited = set()
queue = deque([start])
order = []
while queue:
node = queue.popleft()
if node not in visited:
visited.add(node)
order.append(node)
queue.extend(graph[node])
return order
print(bfs(graph, 'A')) # ['A', 'B', 'C', 'D', 'E']テキスト検索用の転置インデックス
from collections import defaultdict
documents = {
'doc1': 'python is a great programming language',
'doc2': 'data science uses python extensively',
'doc3': 'machine learning with python and data',
}
index = defaultdict(set)
for doc_id, text in documents.items():
for word in text.split():
index[word.lower()].add(doc_id)
def search(query):
return index.get(query.lower(), set())
print(search('python')) # {'doc1', 'doc2', 'doc3'}
print(search('data')) # {'doc2', 'doc3'}PyGWalkerでグループ化データを可視化する
defaultdictでデータをグループ化・集約した後、結果を可視化したいことがよくあります。PyGWalker (opens in a new tab)は、pandasのDataFrameをJupyterで直接インタラクティブな可視化インターフェースに変換します:
from collections import defaultdict
import pandas as pd
import pygwalker as pyg
sales = [
('Electronics', 'Laptop', 1200),
('Electronics', 'Phone', 800),
('Clothing', 'Shirt', 45),
('Clothing', 'Jacket', 120),
]
totals = defaultdict(lambda: defaultdict(int))
for category, product, amount in sales:
totals[category][product] += amount
rows = []
for category, products in totals.items():
for product, total in products.items():
rows.append({'category': category, 'product': product, 'total': total})
df = pd.DataFrame(rows)
walker = pyg.walk(df)FAQ
Pythonのdefaultdictとは?
defaultdictはcollectionsにある辞書のサブクラスで、欠落しているキーにデフォルト値を提供します。KeyErrorを発生させる代わりに、ファクトリ関数(int、list、setなど)を呼び出して、自動的にデフォルト値を作成・保存します。
dictとdefaultdictの違いは?
唯一の機能的な違いは、欠落しているキーの処理方法です。通常のdictはKeyErrorを発生させます。defaultdictはdefault_factory関数を呼び出してデフォルト値を作成します。それ以外のすべての点では同じように動作します。
defaultdict(list)とdefaultdict(set)はいつ使い分けるべき?
要素をグループ化し、重複と挿入順序を保持したい場合はdefaultdict(list)を使用します。キーごとにユニークな要素のみを収集したい場合はdefaultdict(set)を使用します。
defaultdictをJSONにシリアライズできる?
はい、ただしネストされたdefaultdictオブジェクトの場合は、再帰的な変換関数を使って先に通常のdictに変換してください。シリアライズ前の偶発的なキー作成を防ぐために、default_factory = Noneを設定することもできます。
ネストされたdefaultdictの作成方法は?
再帰的なファクトリ関数を定義します:def tree(): return defaultdict(tree)。よりシンプルな2段階のネストには、defaultdict(lambda: defaultdict(int))を使用します。
まとめ
Pythonのcollections.defaultdictは、標準ライブラリで最も実用的なツールの1つです。冗長でエラーが起きやすい辞書蓄積パターンをクリーンなワンライナーに変換します。カウントにはdefaultdict(int)、グループ化にはdefaultdict(list)、ユニークなコレクションにはdefaultdict(set)、階層データにはネストされたdefaultdictを使用してください。
重要なポイント:辞書操作のたびにif key not in dictを書いていることに気づいたら、その辞書をdefaultdictに置き換えてください。コードはより短く、より速く、はるかに保守しやすくなります。