Python|__new__を使ったイミュータブル設計の生成責任整理法
この記事のポイント
- __new__によるイミュータブル生成責任の整理技法を理解する
- 初期化と状態生成をレビューアー視点で明確に読み取る
- init・__new__の責務混在を避ける設計原則を学ぶ
そもそも__new__とは
Pythonの__new__はインスタンスの生成責任を担当する特殊メソッドです。
通常のインスタンス化は以下の順序で動作します。
__new__→ インスタンスメモリ確保(cls引数)__init__→ インスタンス初期化
通常は__init__だけ実装することが多いですが、イミュータブル設計時は__new__の活用が必要になります。
例えば namedtuple や dataclasses(frozen=True) も背後で __new__ を活用しています。
__new__最小例
class MyImmutable:
def __new__(cls, value):
instance = super().__new__(cls)
instance.value = value
return instanceなぜこれをレビューするのか
イミュータブル設計は一貫性・安全性を高める一方で、以下の設計ミスを誘発しやすくなります。
- 初期化責任が
__init__に残留して不整合 __new__と__init__の責務混在- 生成時バリデーション責任の分散
- サブクラス継承時の初期化設計崩壊
レビューアーは「生成責任と初期化責任の分離」を読み解く役割が求められます。
レビューアー視点
- 生成タイミングでの不変性が保証されているか
- バリデーションが初期化後に後出し実行されていないか
__new__内で責務が肥大化していないか- サブクラス拡張時に責務分離が破綻しない構造になっているか
開発者視点
イミュータブル設計時は「すべての状態は構築時点で確定させる」設計方針が重要です。後付けの修正や遅延代入を極力排除する必要があります。
良い実装例
正常設計例:__new__責務明確化
class Coordinate:
__slots__ = ("x", "y")
def __new__(cls, x: float, y: float):
if not (isinstance(x, (int, float)) and isinstance(y, (int, float))):
raise ValueError("座標は数値で指定してください")
instance = super().__new__(cls)
instance.x = x
instance.y = y
return instance良い理由
- 状態確定と検証が生成時点で完了
- __init__は定義不要(責務発生しない)
- 属性は全てイミュータブル状態で確定
__slots__で属性追加禁止
レビュー観点
- __new__は生成責任専任となっているか
- バリデーション責任は必ず生成完了前に整理されているか
- __init__と重複した状態操作が混入していないか
- 状態変更経路は存在しないか
- サブクラス拡張時も責務継承が破綻しない構造になっているか
良くない実装例: ケース1
問題例: __init__に責務残留
class Coordinate:
def __new__(cls, x, y):
instance = super().__new__(cls)
return instance
def __init__(self, x, y):
@Reviewer__init__で状態代入責任を残しています。イミュータブル構造では__new__内で確定させ、__init__を不要化しましょう。 self.x = x
self.y = y問題点
- 生成時に状態未確定
- サブクラス化時に初期化破綻しやすい
- バリデーション責任が呼び出し側依存
改善例
改善後: __new__内で責務完結
class Coordinate:
__slots__ = ("x", "y")
def __new__(cls, x, y):
instance = super().__new__(cls)
instance.x = x
instance.y = y
return instance設計補足
- __new__のみを使用し責務を統一
- __init__廃止で初期化順序混乱を防止
良くない実装例: ケース2
問題例: 遅延状態変更の混入
class Coordinate:
def __new__(cls, x, y):
instance = super().__new__(cls)
instance._pending = (x, y)
@Reviewer生成時点で状態未確定のまま_pendingに保持しています。後続操作依存を生みイミュータブル保証が崩れます。 return instance
def activate(self):
self.x, self.y = self._pending
del self._pending問題点
- 完全初期化が二段階に分断
- activate呼び出し順序依存が発生
- イミュータブル設計崩壊
改善例
改善後: 全状態を__new__時点で確定
class Coordinate:
__slots__ = ("x", "y")
def __new__(cls, x, y):
instance = super().__new__(cls)
instance.x = x
instance.y = y
return instance設計補足
- 状態変化経路は生成時点のみに集中
- 呼び出し順序依存ゼロ化
良くない実装例: ケース3
問題例: サブクラス継承時の破綻
class Coordinate:
def __new__(cls, x, y):
instance = super().__new__(cls)
instance.x = x
instance.y = y
return instance
class LabeledCoordinate(Coordinate):
def __init__(self, x, y, label):
super().__init__(x, y)
@Reviewer__init__で親クラスに再度状態代入を要求しています。サブクラスも__new__へ責務統一すべきです。 self.label = label問題点
- __init__側に責務が残留
- 多重継承時に破綻
改善例
改善後: 継承時も__new__統一
class LabeledCoordinate(Coordinate):
__slots__ = ("label",)
def __new__(cls, x, y, label):
instance = super().__new__(cls, x, y)
instance.label = label
return instance設計補足
- 親子継承でも__new__集中設計
- 多重継承時の一貫性が保たれる
PlantUML設計イメージ
観点チェックリスト
まとめ
__new__は「イミュータブルの初期化肥大を防ぐ武器」と捉えることができます。
レビューアーは生成責任をどこで完了させているのかを常に読み取り、
状態変化の経路が増殖していないかを監視していく役割が求められます。
「生成で全確定、以降は不変」──この設計原則をコードから読み解くのがレビュー技術の中核です。

