Python講座 / メソッドとプロパティによる値の管理

メソッドはオブジェクトの処理を表し、プロパティは属性のような書き方で読み取りや代入の処理を呼び出します。金額を初期化するときと後から更新するときに、同じ検査を通す用途に使えます。

公開する値と内部の保存先を分ける

利用側はitem.amountを読み書きし、実際の値は_amountへ保存します。先頭のアンダースコアは内部用という慣例で、秘密情報を守るセキュリティ機構ではありません。

__init__からself.amountへ代入すると、初期化時もsetterを通ります。初期化だけ検査して、その後の代入は無条件に通す、といった規則の食い違いを避けられます。

初期化と実行時検証は、クラスとインスタンスの基本と型ヒントで引数と戻り値を示すで扱う規則とつながります。

検証付きの支出を実行する

properties.pyに保存し、python properties.pyを実行します。整数の金額だけを受け入れ、boolも拒否するためtype(value) is intを使っています。

class Expense:
    def __init__(self, amount):
        self.amount = amount

    @property
    def amount(self):
        return self._amount

    @amount.setter
    def amount(self, value):
        if type(value) is not int or value < 0:
            raise ValueError("amount must be a nonnegative integer")
        self._amount = value

    def add_fee(self, fee):
        self.amount = self.amount + fee

item = Expense(8000)
item.add_fee(500)
print(item.amount)
try:
    item.amount = -1
except ValueError:
    print("rejected")
print(item.amount)

予想結果は次のとおりです。不正な代入の後も8500が残ります。

8500
rejected
8500

検査を終えてから状態を変更する

setter内でself.amount = valueと書くと、同じsetterが再び呼ばれて再帰します。検証を終えてから内部の_amountへ保存してください。先に値を変えてから例外を出すと、失敗したのに不正な状態が残る設計になります。

属性を読むだけで重い通信や保存処理が起きると、使う側が負荷や失敗を予測しにくくなります。そのような操作は明示的なメソッドにする方が適切です。setterがないプロパティも、内部にある可変オブジェクトまで完全に不変にするわけではありません。

値の規則と操作の規則を分けて試す

True、3.5、-1をそれぞれ代入し、拒否された後のamountが8500のままか確認します。有効な整数へ戻したときは更新できることも確かめます。

この例のadd_feeは更新後の金額が負にならない規則を共有します。しかし「手数料そのものに負の値を許すか」は別の判断です。割引も表すのか、誤入力として拒否するのかを決め、必要ならメソッド側にも検証を加えます。最終状態の制約だけで、すべての操作の意味が決まるわけではありません。

obj.amount を読むとgetter、obj.amount = value で書くとsetterが呼ばれます。@propertyは属性アクセスの規則を定義するもので、利用時に括弧は付けません。検証後に _amount など別の保存属性へ代入します。setter内でself.amountに代入すると再帰呼び出しになります。

項目 動作
obj.amount getterを呼ぶ
obj.amount = 100 setterで検証後_amountへ保存

関連する公式リファレンス