概要(カプセル化は「中身を守り、外からは使いやすい窓口だけ見せる」設計)
カプセル化は、オブジェクトの内部状態(属性)と内部処理(ヘルパーメソッド)を隠し、外部には安全な操作方法だけを提供する考え方です。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
Pythondataclassと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設計が自然に身につきます。
