概要(@propertyは「属性のように使える安全な窓口」)
@propertyは、見た目は属性アクセスなのに、裏側で関数(メソッド)が実行される仕組みです。値の取得(getter)、設定(setter)、削除(deleter)に処理や検証を差し込めるため、カプセル化と使い心地を両立できます。初心者は「内部状態は隠して、@property経由で安全に読み書きする」という型をまず身につけましょう。
基本構文(getter/setter/deleterの三位一体)
最小のパターン
@propertyを付けたメソッドが「取得用の属性」になります。必要なら同名の@name.setterで更新ロジックを、@name.deleterで削除ロジックを用意します。これにより、通常の変数アクセスの書き心地で、裏では検証や計算を走らせられます。
class Person:
def __init__(self, name: str):
self._name = name # 内部用(直接いじらせない)
@property
def name(self) -> str: # getter
return self._name
@name.setter
def name(self, value: str): # setter(検証を挟む)
if not value.strip():
raise ValueError("名前は空不可")
self._name = value
@name.deleter
def name(self): # deleter(必要なら)
del self._name
Pythonproperty()関数で同じことをする
デコレータではなく、組み込み関数property(fget, fset, fdel, doc)でも同様に作れます。fgetがgetter、fsetがsetter、fdelがdeleterで、docは説明文です。
class Rectangle:
def __init__(self, w: float, h: float):
self._w = w; self._h = h
def get_width(self) -> float: return self._w
def set_width(self, v: float):
if v <= 0: raise ValueError("widthは正の数")
self._w = v
def del_width(self): del self._w
width = property(get_width, set_width, del_width, "幅のプロパティ")
Python設計の要点(不変条件・読み取り専用・計算プロパティ)
不変条件を守る更新の入口
壊してはいけない条件(IDは非空、サイズは正、残高は負にならない)をsetterで徹底します。直接代入させず、@propertyの窓口からしか更新できない設計にして、常に整合した状態を保ちます。
class BankAccount:
def __init__(self, balance: int = 0):
if balance < 0: raise ValueError("初期残高は負不可")
self._balance = balance
@property
def balance(self) -> int:
return self._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
Python読み取り専用にして事故を防ぐ
書き換えられると壊れる属性(計算結果、識別子など)はgetterのみ提供して読み取り専用にします。setterを与えなければ、代入は自動的に禁止されます。
class Rectangle:
def __init__(self, w: float, h: float):
if w <= 0 or h <= 0: raise ValueError("正の数")
self._w = w; self._h = h
@property
def area(self) -> float:
return self._w * self._h # 読み取り専用の計算プロパティ
PythonWeb / API文脈の例(安全な公開面と内部封じ込め)
設定値の安全な公開
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クライアントの内部詳細を隠して操作だけ見せる
セッションやヘッダー構築はクラス内部に閉じ込め、外部には安全な操作メソッドだけを公開します。タイムアウトなどはpropertyで検証付きにして、無効値の流入を防ぎます。
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}"})
@property
def timeout(self) -> float:
return self._timeout
@timeout.setter
def timeout(self, v: float):
if v <= 0: raise ValueError("timeoutは正の数")
self._timeout = v
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よくある落とし穴(直接代入・脆い公開・デフォルトの罠)と回避
直接代入で検証をバイパスしない
内部用属性(_nameなど)への外部からの直接代入は不変条件を壊します。公開面(@propertyや明確なメソッド)だけを使う運用にし、レビューで徹底します。
「なんでも公開」で壊れやすくしない
内部の中間状態までプロパティ化すると、依存が増えて脆くなります。利用者が必要とする最小限だけを公開し、内部は隠すのが安全です。
可変デフォルトの共有事故
listやdictをデフォルト引数にすると全インスタンスで共有されます。Noneを受けて内部で新規生成する型にして、各インスタンスが独立した状態を持つようにします。
class Tags:
def __init__(self, tags: list[str] | None = None):
self._tags = [] if tags is None else list(tags)
@property
def tags(self) -> list[str]:
return list(self._tags) # 防御的コピーで外部からの破壊を防ぐ
Python例題(注文オブジェクトを@propertyで安全運用)
状態遷移と整合性を“窓口”で守る
注文の状態・品目を内部に閉じ、読み取りはsummaryだけ、更新はメソッドだけにします。これで順序や数量の不変条件を強制できます。
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は「使いやすさ」と「安全性」を同時に叶える)
@propertyは、属性アクセスの心地よさを保ちながら、取得・設定・削除に検証や整形を差し込める強力な“窓口”です。カプセル化の核として、内部用は_で隠し、公開は@propertyで最小限に。不変条件はsetterや更新メソッドで必ず守り、読み取り専用・計算プロパティを活用する。これを型として徹底すれば、初心者でも「壊れないのに使いやすい」OOP設計を自然に作れます
