1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

#0184(2025/07/01) Pythonの型とミュータブル/イミュータブル

1
Posted at

Pythonの型とミュータブル/イミュータブル完全ガイド

はじめに

  • Python では 「型」(type) と「ミュータビリティ」(mutability) が実行時の挙動を左右する。
  • ミュータブルかイミュータブルかで、関数引数の副作用・スレッドセーフ設計・メモリ効率が変わる。
  • 本記事では標準型を軸に、設計で迷わないための実践的ノウハウを整理する。

1. Python 型システムの全体像

1‑1. 数値系 (Numeric)

  • int: 任意精度整数。イミュータブル。
  • float: IEEE 754 倍精度。イミュータブル。
  • complex: 実部・虚部を持つ複素数。イミュータブル。

1‑2. シーケンス系 (Sequence)

  • list: 可変長配列。ミュータブル
  • tuple: 不変タプル。イミュータブル
  • range: 遅延シーケンス。イミュータブル。
  • str, bytes: 文字列/バイト列。イミュータブル。
  • bytearray: ミュータブルなバイト列。

1‑3. マッピング系 (Mapping)

  • dict: ハッシュマップ。ミュータブル
  • types.MappingProxyType: 読み取り専用ラッパー。イミュータブル風味。

1‑4. 集合系 (Set)

  • set: ハッシュ集合。ミュータブル
  • frozenset: 不変集合。イミュータブル。

1‑5. ユーザー定義

  • クラス/dataclass/NamedTuple など。__slots__frozen=True でイミュータブル化可能。

2. ミュータブル vs イミュータブル を見分ける

2‑1. 定義

  • ミュータブル: インスタンス生成後に 内部状態を変えられる
  • イミュータブル: 変更不可。演算すると 新しいオブジェクト が返る。

2‑2. 標準型の分類早見表

  • ✅ ミュータブル: list, dict, set, bytearray, カスタムクラス(デフォルト)
  • ❌ イミュータブル: int, float, complex, str, bytes, tuple, frozenset, range

2‑3. “見かけ倒し”に注意

  • tuple はイミュータブルだが、内部にミュータブル要素を持つと全体としては書き換え可能。

    t = ([],)
    t[0].append(1)  # タプル自体は変更せず、中のリストを書き換え
    
  • C 拡張で実装された型 (numpy.ndarray など) は独自のミュータビリティルールを持つ。


3. 関数呼び出しと副作用

3‑1. Python の引数評価モデル

  • Pass‑by‑object‑reference: 参照(オブジェクトのアドレス値)を丸ごとコピー。
  • ミュータブルなら同じオブジェクトを共有、イミュータブルなら新オブジェクト生成が起こりうる。
def push(lst, x):
    lst.append(x)             # ミュータブル → 呼び出し元に影響

def add_one(n):
    n += 1                    # イミュータブル → 元は不変

3‑2. 破壊的操作 vs 非破壊的操作

  • リスト操作の例: list.sort() は破壊的 (in‑place)、sorted(list) は非破壊的。
  • API 設計時は 命名で意図を明示 (例: update_ prefix を付ける)。

3‑3. デフォルト引数の罠

def bad_append(elem, lst=[]):
    lst.append(elem)
    return lst
bad_append(1)  # -> [1]
bad_append(2)  # -> [1, 2]  ← 意図せず共有
  • デフォルト値は 関数定義時に一度だけ評価され、ミュータブルだと全呼び出しで共有される。
  • 対策: None sentinel パターン。

4. 実務パターン

4‑1. 不変型で安全に受け渡す

  • マルチスレッドや asyncio で共有データを扱うときは tuple, frozenset, types.MappingProxyType で防御的コピー。

4‑2. dataclass(frozen=True) で DTO を定義

from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
    x: float
    y: float
  • イミュータブルなので hash() 可能。辞書キーやセット要素に安全。

4‑3. コピー戦略の選択

シナリオ 推奨手段
浅い階層で十分 copy.copy()
ネスト構造をまるごと複製 copy.deepcopy()
大規模配列/画像 numpy.copy() or tensor.clone()

4‑4. メモリ節約テクニック

  • __slots__ で属性辞書を削除し、1 インスタンスあたり数十バイト削減。
  • イミュータブル化すると GC の追跡対象外になり、トレース負荷が低減。

5. テスト・デバッグの観点

  • Stateful テスト: ミュータブルな戻り値はテスト後に初期化しておく。
  • 参照カウント確認: sys.getrefcount(obj) で意図しない循環参照を検出。
  • メモリプロファイル: tracemalloc でスナップショットを比較し、コピー回数過多をあぶり出す。

6. ベストプラクティスまとめ

  • ミュータブルを渡すときは副作用をドキュメント
  • 変更より生成を優先: Functional プログラミングライクなコードはバグ率が下がる。
  • 巨大データ構造は copy-on-write 方針: PyArrow や pandas はスライスをコピーせず共有する設計。
  • イミュータブル型をハッシュキーに使うのが鉄則
  • C 拡張や外部ライブラリの型は公式ドキュメントで mutability を必ず確認

まとめ

Python では「参照のコピー」が基礎ルールだが、ミュータビリティの有無で副作用が生じるかどうかが決まる。設計初期にデータのライフサイクルを決め、mutable と immutable を使い分ければ、安全で高速なコードベースを維持できる。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?