Python の dataclass を使い始めてしばらくすると、必ず遭遇するバグがあります。
そして原因はだいたい mutable(可変)なデフォルト値。
今日はこの罠を避けるための default_factory を、かみ砕いて解説します。
✅ 結論
list / dict / set などの可変オブジェクトをデフォルト値にしたい場合、必ず
default_factoryを使うべき!
🔥 初心者が必ずやる「危険な例」
from dataclasses import dataclass
@dataclass
class User:
tags: list = []
一見良さそうに見えますが…
u1 = User()
u2 = User()
u1.tags.append("python")
print(u1.tags) # ['python']
print(u2.tags) # ['python'] ←!??
なぜか u2 にも値が反映されてしまいます。
❓なぜこうなるの?
理由は Python の仕様。
- デフォルト引数(=
tags=[])は - 定義時に一度だけ評価される
つまり、この [] は
全インスタンスで共有されるリストになってしまうのです。
Python界隈では「可変デフォルト引数の罠」と呼ばれる有名な事故ポイント。
✅ 正しい書き方:default_factory=list
from dataclasses import dataclass, field
@dataclass
class User:
tags: list = field(default_factory=list)
動作を確認:
u1 = User()
u2 = User()
u1.tags.append("python")
print(u1.tags) # ['python']
print(u2.tags) # []
今度は独立しました 🎉
🧠 default_factory の仕組み
default_factory=list は内部的には
tags = list()
として インスタンスごとに新しいリストを生成します。
✅ どんなときに default_factory を使う?
| 型 | default OK? | コメント |
|---|---|---|
| list | ❌ | 共有バグの原因 |
| dict | ❌ | 共有されるので危険 |
| set | ❌ | 同上 |
逆に不可変な型は OK:
| 型 | default OK? | コメント |
|---|---|---|
| int | ✅ | 変更不能 |
| float | ✅ | 同上 |
| str | ✅ | 同上 |
| tuple | ✅ | 同上 |
✅ 実務でよく見るパターン
部下IDを管理するエージェントモデル:
@dataclass
class Agent:
subordinate_ids: list = field(default_factory=list)
あるあるですね。
🧩 default と default_factory の違いまとめ
| default | default_factory | |
|---|---|---|
| 評価タイミング | 定義時に一回 | インスタンス生成ごと |
| 可変オブジェクト | ❌危険 | ✅安全 |
| 不可変オブジェクト | ✅OK | ✅OK |
✅ 関数(callable)なら何でも渡せる
field(default_factory=dict)
field(default_factory=set)
field(default_factory=lambda: [1, 2, 3])
例:タイムスタンプ自動生成
from datetime import datetime
@dataclass
class Log:
timestamp: datetime = field(default_factory=datetime.now)
❌(補足)「それでも default で良いよね?」は危険
共有バグは
- 発生頻度低い
- 再現性が低い
- 見落としやすい
そして本番事故になりやすい。