defaultdict
: 条件のない辞書の進化
Pythonには数多くの外部ライブラリがありますが、標準ライブラリだけを正しく理解しても、実務で十分に強力なコードを書くことができます。今回はその中でも collections.defaultdict
を深く掘り下げていきます。
この記事を通して、単なる概念紹介を超えて、実際にいつ、なぜ、どのように defaultdict
を使うべきかを明確に理解できるようになるでしょう。
collectionsシリーズの第一弾、Counterクラスについて興味がある方は、以前の記事を読むことをお勧めします。 Python標準ライブラリ ① - collections.Counter
1. 基本概念: defaultdict
とは?
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'
にアクセスすると、 KeyError
なしで 0
が自動的に生成され、その後 +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
が持つ決定的な利点 3つ
defaultdict
は dict
+ 条件文
パターンを多く使用する状況で、 可読性、保守性、安全性 を同時に高めてくれます。特に次の3つの状況で「これは絶対に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
1行のコードでエラー防止 + 可読性向上 + 保守性向上が実現できれば、
defaultdict
は単なる便利機能ではなく、 Pythonらしい考え方の核心的なツールと言えるでしょう。
次に扱うテーマは pathlib
です。
ファイル/ディレクトリを扱うコードを オブジェクト指向的に変えてくれるモダンな方法で、初心者から中級者まで誰でも便利に活用できます。 os.path
に馴染みのある開発者の皆さんは「わあ!os.pathよりずっと簡単だ!」という感覚を感じることができるでしょう。次回もお楽しみに。
Add a New Comment