メインコンテンツへスキップ

dataclassのミュータブルとイミュータブルの違いと使い分け

🐍はじめに

ここでは、dataclassでの「ミュータブルとイミュータブルの違い」と、その使い分けの指針について解説していきます!


⚔️ ミュータブル vs イミュータブル:違いと比較

項目 ミュータブル (frozen=False, デフォルト) イミュータブル (frozen=True)
属性の変更 後から変更できる 変更できない(エラーになる)
__hash__ 自動で付かない(辞書キーに使えない) 自動で付く(辞書キーに使える)
バグ耐性 変更による意図しない副作用が起きうる 副作用がないため安全
関数型スタイルとの相性 原則NG(変更が前提) 非常に良い
共有時の安心感 他の関数で書き換えられる可能性あり どこからも変更されない安心感
再利用性 状態依存で再利用しづらいことも 常に同じなので再利用しやすい

ミュータブルは「変化」を、イミュータブルは「安定性」を重視した選択になります。


🧭 使い分けの指針:どう選ぶ?

✅ ミュータブルを使うべき場面

  • 「状態」を持つオブジェクト(例:ゲームキャラクター、セッション情報など)

  • オブジェクトの一部の情報だけを頻繁に更新したいとき

  • 外部APIとのやりとりで「値を上書きして送る」ような場合

例:ユーザーが編集できる設定情報など

@dataclass
class Settings:
    theme: str
    font_size: int

ミュータブルなら .font_size = 16 のように変更OK!


✅ イミュータブルを使うべき場面

  • 一度決めたら変わらないもの(例:ユーザー登録情報、計算結果、識別子など)

  • **辞書のキーや集合(set)**に使いたいとき

  • 参照されるだけの安全な値を共有したいとき

  • テストしやすく保守性の高いコードを書きたいとき

例:ユーザーのプロフィール情報など(登録後は基本的に変更不可)

@dataclass(frozen=True)
class Profile:
    id: int
    username: str

「外から書き換えられたら困るデータ」はイミュータブルにすると事故防止になります!


🎓 補足:部分だけ凍らせたいときはどうする?

実は、dataclass全体を frozen=True にしなくても、内部にイミュータブルな型を使うことで安全性を高められます。

from typing import NamedTuple

class Address(NamedTuple):  # NamedTupleはイミュータブル
    city: str
    zip: str

@dataclass
class Customer: # Customer自身はfrozon=Trueがついていないのでミュータブル
    name: str
    address: Address  # イミュータブルな部分

こうすることで、「この部分だけは絶対変えない」という設計もできます!


🧩 最後に:変えられる vs 変えたくない

  • 変えたい時 → ミュータブル

  • 変えたくない時 → イミュータブル

というのは大前提ですが、それ以上に重要なのは、その意図がコードで明示されることです。

他人にとっても「なるほど、これは変えちゃダメな値なんだな」と一目で分かる設計は、読みやすさと信頼性の両方に繋がります!