Python | OOP:カプセル化

Python Python
スポンサーリンク
  1. 概要(カプセル化は「中身を守り、外からは使いやすい窓口だけ見せる」設計)
  2. 基本方針(公開インターフェースと内部実装を分ける)
    1. 公開面は「少なく、明確に」
    2. 内部面は「検証と整合性維持」に集中
  3. Pythonでの実装手段(慣習・名前マングリング・プロパティ)
    1. 慣習で意図を示す(_name は内部用)
    2. 名前マングリング(__name は強く隠す)
    3. @property で安全な読み書きを提供
  4. 不変条件と更新の入口(壊れない状態管理の型)
    1. 不変条件(invariants)を明文化して守る
    2. 更新の入口を「一箇所」に集約
  5. Web / API 文脈の実例(セッション・設定・レスポンス整形)
    1. クライアントの内部を隠し、操作を限定する
    2. 設定は不変寄せで安全に公開
    3. レスポンスの揺れをモデルで吸収
  6. 実装の工夫(slots・dataclass・可変デフォルトの回避)
    1. slotsで属性を固定してタイプミス・メモリ削減
    2. dataclassとpropertyの組み合わせ
    3. 可変デフォルトはNone受け→内部で新規生成
  7. よくある落とし穴と回避(隠蔽しすぎ・直書き・暗黙のルール)
    1. 隠蔽しすぎて使いづらい
    2. 直接書き換えで検証をバイパス
    3. 暗黙の依存を内部に密かに持つ
  8. 例題(カプセル化で安全な小さな注文サービス)
    1. 注文の状態と整合性を守る
  9. まとめ(「触ってよい窓口」だけを提供し、内部はルールで守る)

概要(カプセル化は「中身を守り、外からは使いやすい窓口だけ見せる」設計)

カプセル化は、オブジェクトの内部状態(属性)と内部処理(ヘルパーメソッド)を隠し、外部には安全な操作方法だけを提供する考え方です。Pythonには厳密なアクセス修飾子はありませんが、命名規約(_name, __name)と@propertyで「触っていい面」と「触るべきでない中身」を明確にできます。重要なのは、不変条件(壊してはいけないルール)を守り、更新の入口を一箇所に集約することです。


基本方針(公開インターフェースと内部実装を分ける)

公開面は「少なく、明確に」

外部利用者が触るのは、目的ごとの短いメソッドや@propertyだけに絞ります。内部にある補助関数・中間状態は見せないほど、誤操作や偶発的な破壊が減ります。

内部面は「検証と整合性維持」に集中

内部属性は直接書き換えられると壊れやすいので、更新の入口(setterやメソッド)で検証と整合性の維持を行います。これで「いつでも正しい状態」が保てます。


Pythonでの実装手段(慣習・名前マングリング・プロパティ)

慣習で意図を示す(_name は内部用)

先頭にアンダースコア(_)を付けると「外部から触るな(壊れやすい)」のサインになります。強制ではありませんが、広く使われる合図です。

class Account:
    def __init__(self, balance: int):
        self._balance = balance  # 内部用
Python

名前マングリング(__name は強く隠す)

二重アンダースコア(__secret)は、クラス名に基づいて内部名へ変換され、意図しない上書き・衝突を避けます。必要最小限で使い、過度な隠蔽は避けます。

class Secure:
    def __init__(self):
        self.__token = "secret"  # 外部から直接は触れにくい
Python

@property で安全な読み書きを提供

直接書き換えられると壊れる属性は、プロパティで公開し、setterで検証します。計算値や読み取り専用もpropertyで自然に扱えます。

class Rectangle:
    def __init__(self, w: float, h: float):
        self._w = w; self._h = h

    @property
    def width(self) -> float:
        return self._w

    @width.setter
    def width(self, v: float):
        if v <= 0:
            raise ValueError("widthは正の数")
        self._w = v

    @property
    def area(self) -> float:
        return self._w * self._h  # 読み取り専用の計算値
Python

不変条件と更新の入口(壊れない状態管理の型)

不変条件(invariants)を明文化して守る

「残高は負にならない」「幅と高さは正の数」「IDは非空」など、壊すと致命的な条件を定義し、更新時に必ずチェックします。これがカプセル化の核心です。

class BankAccount:
    def __init__(self, balance: int = 0):
        if balance < 0:
            raise ValueError("初期残高は負にできません")
        self._balance = balance

    def deposit(self, amount: int):
        if amount <= 0:
            raise ValueError("入金は正の額")
        self._balance += amount

    def withdraw(self, amount: int):
        if amount <= 0 or amount > self._balance:
            raise ValueError("引き出し額が不正")
        self._balance -= amount

    @property
    def balance(self) -> int:
        return self._balance
Python

更新の入口を「一箇所」に集約

属性を直接書き換えず、更新はメソッド・setterだけに通すと、検証漏れがなくなります。外からは「できること」だけ呼べるようにします。


Web / API 文脈の実例(セッション・設定・レスポンス整形)

クライアントの内部を隠し、操作を限定する

セッションやヘッダーの詳細はクラス内に閉じ込め、外からは「エンドポイント呼び出し」だけ提供します。

import requests

class ApiClient:
    def __init__(self, base_url: str, api_key: str, timeout: float = 5.0):
        if not base_url.startswith("https://"):
            raise ValueError("HTTPSのみ許可")
        self._base_url = base_url
        self._timeout = timeout
        self._session = requests.Session()
        self._session.headers.update({"Authorization": f"Bearer {api_key}"})

    def get_user(self, uid: str) -> dict:
        r = self._session.get(f"{self._base_url}/users/{uid}", timeout=self._timeout)
        r.raise_for_status()
        return r.json()
Python

設定は不変寄せで安全に公開

キーやURLは書き換えで事故が起きやすいので、読み取り専用に。

class Settings:
    def __init__(self, base_url: str, api_key: str):
        self._base_url = base_url
        self._api_key = api_key

    @property
    def base_url(self) -> str: return self._base_url
    @property
    def api_key(self) -> str: return self._api_key
Python

レスポンスの揺れをモデルで吸収

最低限の保証をモデルへ詰めて、下流へ安全に渡します。

class UserModel:
    def __init__(self, data: dict):
        uid = data.get("id")
        if not uid:
            raise ValueError("idは必須")
        self._uid = str(uid)
        self._name = data.get("name") or "unknown"

    def to_dict(self) -> dict:
        return {"id": self._uid, "name": self._name}
Python

実装の工夫(slots・dataclass・可変デフォルトの回避)

slotsで属性を固定してタイプミス・メモリ削減

大量インスタンスでは、許可属性を固定し、余計な属性追加を防げます。

class Point:
    __slots__ = ("x", "y")
    def __init__(self, x: float, y: float):
        self.x = x; self.y = y
Python

dataclassとpropertyの組み合わせ

データ容器にはdataclass、検証が必要な属性はpropertyに切り分けると読みやすく保てます。

from dataclasses import dataclass

@dataclass
class PriceDTO:
    amount: float
    currency: str = "JPY"
Python

可変デフォルトはNone受け→内部で新規生成

共有事故を防ぎ、インスタンスごとに独自のコレクションを持たせます。

class Bag:
    def __init__(self, items: list | None = None):
        self._items = [] if items is None else list(items)
Python

よくある落とし穴と回避(隠蔽しすぎ・直書き・暗黙のルール)

隠蔽しすぎて使いづらい

全部を_で塞ぐと拡張が困難に。基本は「で内部を示し、公開面は@propertyと明確なメソッド」で十分です。

直接書き換えで検証をバイパス

内部属性へ外部から代入すると不変条件が壊れます。公開面からしか更新できない設計にして、レビューで徹底します。

暗黙の依存を内部に密かに持つ

setterや更新メソッドで「他属性との整合性」まで必ず調整します。連動関係は一箇所に集約し、分散させないこと。


例題(カプセル化で安全な小さな注文サービス)

注文の状態と整合性を守る

class Order:
    def __init__(self, order_id: str):
        if not order_id:
            raise ValueError("order_id必須")
        self._id = order_id
        self._items: list[tuple[str, int]] = []
        self._status = "init"  # init, placed, shipped

    def add_item(self, sku: str, qty: int):
        if self._status != "init":
            raise ValueError("確定後は追加不可")
        if not sku or qty <= 0:
            raise ValueError("SKUと数量が不正")
        self._items.append((sku, qty))

    def place(self):
        if not self._items:
            raise ValueError("商品がない注文は確定不可")
        self._status = "placed"

    def ship(self):
        if self._status != "placed":
            raise ValueError("未確定の注文は出荷不可")
        self._status = "shipped"

    @property
    def summary(self) -> dict:
        return {"id": self._id, "items": list(self._items), "status": self._status}
Python

更新入口に検証を集約し、不変条件(状態遷移の順序)を守る安全な設計です。


まとめ(「触ってよい窓口」だけを提供し、内部はルールで守る)

カプセル化の要は、公開インターフェースを最小で明確にし、内部を@propertyや更新メソッドで防御することです。命名規約(_ / __)で意図を示し、検証で不変条件を守る。更新は一箇所に集約し、外部からの直書きを許さない。Web/APIではセッション・設定・レスポンス整形を内部へ隠し、安全な操作だけ露出する。この型を徹底すれば、初心者でも「壊れない・使いやすい・拡張しやすい」OOP設計が自然に身につきます。

タイトルとURLをコピーしました