- 概要(Pythonの“private”的記述は「強制」ではなく“合図”と“窓口設計”)
- 慣習による“内部用”合図(_name と「外から直接触らない」約束)
- 名前マングリング(__name)で“強めに隠す”仕組みと注意点
- propertyで「安全な窓口」を作る(読み取り専用・検証付き書き込み)
- 実務的な“private”設計(APIクライアント・設定・モデルの隠蔽)
- 強めの“非公開”が必要なときの選択肢(slots・frozen dataclass・合成)
- よくある落とし穴と対策(“隠すだけ”で満足しない・過度な __・ログ漏れ)
- 例題(“private的”設計で安全な注文ドメイン)
- まとめ(Pythonの“private”は「隠す記号」より“窓口+不変条件”で守る)
概要(Pythonの“private”的記述は「強制」ではなく“合図”と“窓口設計”)
PythonにはJavaのprivateのような強制的なアクセス修飾子はありません。代わりに「内部用」を示す命名規約(先頭にアンダースコア)と、二重アンダースコアによる名前マングリング(擬似的な隠蔽)、そして@propertyで安全な読み書きの窓口を作る——この3本柱で“壊れない設計”を実現します。要点は「直接触らせず、必ず窓口を通る」ことと、「不変条件を更新の入口で守る」ことです。
慣習による“内部用”合図(_name と「外から直接触らない」約束)
単一アンダースコア(_name)は「内部用」の合図
Pythonでは、先頭にアンダースコアを付けた属性・メソッドを“内部用”として扱う慣習があります。外部からアクセスは可能ですが、「触ると壊れやすい」ことを伝えるサインです。モジュールレベルでも、from module import * の対象外になるため、公開面を絞る効果があります。
class Account:
def __init__(self, balance: int):
self._balance = balance # 内部用(直接いじらせない)
def deposit(self, amount: int):
if amount <= 0:
raise ValueError("入金は正の額")
self._balance += amount
@property
def balance(self) -> int:
return self._balance
Pythonこのように“内部用”をアンダースコアで示し、公開面(depositやbalance)だけを触ってもらうと、不変条件を崩されません。
名前マングリング(__name)で“強めに隠す”仕組みと注意点
二重アンダースコア(__secret)はクラス名に基づいて変形される
クラス内で先頭に二重アンダースコアを付けると、属性名は _ClassName__secret の形に変換され、偶発的な上書きや衝突を避けられます。外部からの直接アクセスがしにくくなるため、“強めの隠蔽”として使えます。
class Secure:
def __init__(self):
self.__token = "secret" # 外からは見えにくい
def masked(self) -> str:
t = self.__token
return t[:4] + "..."
s = Secure()
# s.__token は AttributeError
print(s._Secure__token) # 実はこうすれば触れてしまう(やってはいけない)
Pythonこのように「完全に触れない」わけではないため、“悪用防止”ではなく“衝突回避・誤操作防止”として理解してください。テストやモックが必要なクラスでは、二重アンダースコアの多用は避け、単一アンダースコア+明確な窓口のほうが扱いやすいです。
propertyで「安全な窓口」を作る(読み取り専用・検証付き書き込み)
読み取り専用や検証付きのsetterで不変条件を守る
内部属性を直接公開せず、@propertyで読み取り専用にしたり、setterで検証を挟むのが“Python流のprivate”です。更新ロジックを一箇所に集約し、必ず条件を満たすようにします。
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“直接代入”を禁じるだけでなく、「必ずこの窓口を通る」という運用にすると、壊れない状態管理が自然に保てます。
実務的な“private”設計(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設定は不変寄せで“private的”に公開
APIキーやベース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モデルで揺れるレスポンスを吸収してから公開
最低限の保証を内部に閉じ、to_dictなどの“安全な表現”だけを外へ渡します。
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・frozen dataclass・合成)
slotsで属性追加を禁止し、メモリも削減
大量インスタンスで“余計な属性の混入”を防ぎたいときに有効です。タイプミスで新しい属性が生まれる事故も防げます。
class Point:
__slots__ = ("x", "y")
def __init__(self, x: float, y: float):
self.x = x; self.y = y
Pythondataclass(frozen=True)で実質的な読み取り専用
生成後に書き換え不可のデータ容器が作れます。更新は“新インスタンス作成”で表現します。
from dataclasses import dataclass
@dataclass(frozen=True)
class PriceDTO:
amount: float
currency: str = "JPY"
Python合成(has-a)で内部機能をクラス外に隔離
「見せたくない機能(キャッシュ、暗号化鍵管理など)」は専用クラスに隔離し、窓口クラスはその結果だけを受け取る設計にすると、意図しないアクセスを構造的に防げます。
よくある落とし穴と対策(“隠すだけ”で満足しない・過度な __・ログ漏れ)
“隠す”だけでは安全にならない
アンダースコアも名前マングリングも、悪意あるアクセスを完全には防げません。鍵やトークンなどは「そもそもログや例外に出さない」「保存しない」運用のほうが重要です。
二重アンダースコアの過剰使用でテストが困難に
クラス内部からしか触れない設計は、モックや差し替えが難しくなります。単一アンダースコア+明確な窓口のほうが現実的です。
デバッグで秘密をprintする誤習慣
マスク出力や要約に留める習慣を徹底してください。生の値をログへ出さないことが、コードより強い“private”になります。
例題(“private的”設計で安全な注文ドメイン)
更新の入口を一箇所に集約し、不変条件を守る
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このように、内部状態(_items, _status)には直接触れさせず、操作の窓口(add_item, place, ship)と読み取りの窓口(summary)だけを公開します。これがPythonにおける“private的”設計の完成形です。
まとめ(Pythonの“private”は「隠す記号」より“窓口+不変条件”で守る)
Pythonは強制的なprivateを持ちません。だからこそ、慣習(_name)、名前マングリング(__name)、property(検証付き窓口)を組み合わせて「外からは安全な操作だけできる」設計にするのが本質です。重要なのは、更新の入口を一箇所に集約し、不変条件を常に守ること。必要に応じてslotsやfrozen dataclass、合成で“触れさせない構造”を選ぶ。これを型として徹底すれば、初心者でも「壊れない・漏れない・使いやすい」private的なOOP設計を自然に体得できます。
