#0147（2025/05/24）Python `@dataclass` 

# Python `@dataclass` 


## 1 `@dataclass` とは何か

Python 3.7 で追加された **データクラス** は、データ保持を目的としたクラス宣言を劇的に簡潔にする言語機能です。通常クラスを書けば `__init__`, `__repr__`, `__eq__` など数行〜数十行を手書きするところを、**1 行のデコレータ**で自動生成してくれます。加えて、静的型付け (`PEP 526`) と相性がよく、IDE の補完・型チェックも向上します。

メリットは次の 3 点に集約されます。

1. **記述量の削減** — メソッド自動生成でボイラープレートから解放。
2. **型安全性の向上** — 型ヒント強制 & 静的解析しやすい AST を生成。
3. **コードの意図が明確** — 「これはロジック中心のクラスではなく“データ構造”だ」と宣言できる。

---

## 2 基本的な使い方

```python
from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int

a = Person("username", 30)
print(a)            # => Person(name='username', age=30)
print(a == Person("username", 30))  # => True
```

生成されるメソッド（抜粋）

| メソッド       | 役割                  |
| ---------- | ------------------- |
| `__init__` | フィールドを引数に受け取って代入    |
| `__repr__` | デバッグ向け公式文字列表現       |
| `__eq__`   | フィールド値で等価比較         |
| `__hash__` | `frozen=True` 時のみ生成 |

---

## 3 主要オプション & 利用場面

### 3‑1 `frozen=True` — イミュータブル化

```python
@dataclass(frozen=True)
class Point:
    x: int
    y: int

p = Point(1, 2)
# p.x = 10  # => dataclasses.FrozenInstanceError
```

**利用場面**

* 設定値や座標ベクトルなど「絶対に書き換えたくない」構造体。
* `set`・`dict` キーに安心して使いたいとき（ハッシュ化）。

### 3‑2 `order=True` — 大小比較を自動生成

```python
@dataclass(order=True)
class Score:
    total: int
    bonus: int

s1 = Score(80, 5)
s2 = Score(80, 10)
assert s1 < s2  # (total, bonus) タプル比較
```

**利用場面**

* ソートキーを簡潔に持たせたいレコード（スコア表、バージョン番号など）。
* `heapq` など順序付きデータ構造と一緒に使う場合。

### 3‑3 `field(repr=False)` — 機密情報を隠す

```python
from dataclasses import dataclass, field

@dataclass
class User:
    username: str
    password: str = field(repr=False)

print(User("username", "secret"))  # => User(username='username')
```

**利用場面**

* パスワード・トークン・API キーの誤出力防止。
* 大容量バイナリなどログに出すとノイズになるフィールドを隠蔽。

### 3‑4 `init=False` — コンストラクタ引数から除外

```python
@dataclass
class Rectangle:
    width: float
    height: float
    area: float = field(init=False)

    def __post_init__(self):
        self.area = self.width * self.height
```

**利用場面**

* 引数として与えず、他フィールドから派生計算する値。
* DB ID・タイムスタンプを自動生成したいケース。

### 3‑5 `field(default_factory=list)` — ミュータブルなデフォルト値

- `default=[]` と書くと 1 つのリストインスタンス がクラス全体で共有される（※関数デフォルト引数と同じ罠）。
- `default_factory=list` は インスタンス生成時に list() を呼び出して “毎回” 新しいリストを作る ので安全。

```python
# 悪い例：共有されてしまう
@dataclass
class Bad:
    items: list[str] = []

a = Bad(); b = Bad()
a.items.append("X")
print(b.items)  # ['X']  ← 共有

# 良い例：インスタンスごとに独立
@dataclass
class Good:
    items: list[str] = field(default_factory=list)

c = Good(); d = Good()
c.items.append("Y")
print(d.items)  # []     ← 分離
```

**利用場面**

* リスト・辞書など可変オブジェクトを安全にデフォルト化。
* 関数引数の「`foo=[]` 罠」を避けるのと同じ理由。

---

## 4 `pydantic.BaseModel` との比較

| 目的         | `@dataclass`              | `BaseModel`                 |
| ---------- | ------------------------- | --------------------------- |
| データ保持構造体   | ◎ 最小依存で標準ライブラリ完結          | ◯ （可能）                      |
| 入力バリデーション  | △ 手動実装が必要                 | **◎ 自動検証 + 型変換（str→int 等）** |
| JSONシリアライズ | △ `asdict` + `json.dumps` | ◎ `.json()`, `.dict()`      |
| 依存ライブラリ    | なし                        | `pydantic`（外部）              |
| FastAPI 連携 | △                         | **◎ デフォルト**                 |

結論として、**「純粋なデータ構造」** には `@dataclass`、**「外部 I/O の検証・スキーマ」** には `BaseModel` が適材適所です。

---

## 5 まとめ & ベストプラクティス

* **ボイラープレート削減** → まずは素直に `@dataclass` を使う。
* **イミュータブル or ソート可能** → `frozen=True`, `order=True` で宣言のみで実現。
* **機密データ vs ログ** → `field(repr=False)` で安全。
* **派生値 & ミュータブル既定値** → `init=False` + `__post_init__`, `default_factory` が王道。
* **API 入出力検証** → `pydantic.BaseModel` と使い分け。

データクラスを“**小さな構造体**”として捉え、振る舞いが複雑化したら通常クラス／パターンに昇格させるのが、保守性の高い Python コードへの近道です。