🤖

@propertyとは

2024/02/11に公開

今回はPythonの@propertyについて軽く解説します。

@propertyとは

@propertyは、クラスのインスタンス変数を簡潔に呼び出せるようにするための、組み込みのデコレータです。

デコレータは装飾される関数を引数として取り、新しい関数を返す高階関数ですが、@propertyはその関数における特殊メソッドをオーバーライドし、関数の呼び出し方によって返す関数を変更します。

言葉で表すと非常に難解なので、例を見ていきましょう。

class Person:
    def __init__(self, name):
        self._name = name

    @property # getter
    def name(self):
        return self._name

    @name.setter # setter
    def name(self, value):
        if not value:
            raise ValueError("名前は空にできません")
        self._name = value

    @name.deleter # deleter
    def name(self):
        print("名前の削除")
        del self._name

taro = Person("太郎") # 値の取得を行う。getterが呼び出される
print(taro.name) 
# 太郎
taro.name = "山田太郎" # 値の設定を行う。setterが呼び出される
print(taro.name)
# 山田太郎
del taro.name # 値の削除を行う。deleterが呼び出される
# 名前の削除
print(taro.name) 
# AttributeError: 'Person' object has no attribute '_name'

@propertyを使用すると、そのメソッドはgetterとなり、値を取得する際に呼び出されます。そして、@メソッド名.setterと@メソッド名.deleterでデコレートされた関数がそれぞれsetterとdeleterとなり、値の代入、及び削除を行う際に呼び出されます。

これらのデコレートにより内部的には、__get__, __set__, __delete__の操作がオーバーライドされ、それぞれの操作に対して、別の関数を返すように設定されています。

内部処理のイメージ
# 実際はC言語で記述されている(CPython)
# 処理の内容をpythonで擬似再現

class property:
    def __init__(self, fget=None, fset=None, fdel=None):
        self.fget = fget # ゲッター関数(なくてもよい)
        self.fset = fset # セッター関数(なくてもよい)
        self.fdel = fdel # デリーター関数(なくてもよい)

    def getter(self, fget):
        """ getter関数の更新 """
        """ @[メソッド名].getterでデコレートした関数がfget """
        """ ゲッター関数を更新した新たなpropertyを返す """
        return property(fget=fget, fset=self.fset, fdel=self.fdel)

    def __get__(self, obj, objtype=None): # オーバーライド
        if self.fget is None:
            raise AttributeError("unreadable attribute") # ゲッター関数がなければエラー
        return self.fget(obj) # ゲッター関数を呼び出す


    def setter(self, fset): 
        """ setter関数の更新 """
        """ @[メソッド名].setterでデコレートした関数がfset """
        """ セッター関数を更新した新たなpropertyを返す """
        return property(fget=self.fget, fset=fset, fdel=self.fdel)

    def __set__(self, obj, value): # オーバーライド
        if self.fset is None:
            raise AttributeError("can't set attribute") # セッター関数がなければエラー
        return self.fset(obj, value) # セッター関数を呼び出す


    def deleter(self, fdel):
        """ deleter関数の更新 """
        """ @[メソッド名].deleterでデコレートした関数がfdel """
        """ デリーター関数を更新した新たなpropertyを返す """
        return property(fget=self.fget, fset=self.fset, fdel=fdel)

    def __del__(self, obj, value): # オーバーライド
        if self.fdel is None:
            raise AttributeError("can't set attribute") # セッター関数がなければエラー
        return self.fdel(obj, value) # デリーター関数を呼び出す

メリット

  • 可読性の向上
    @propertyを使用することで、get_name()のようなメソッドを定義しなくても、インスタンス変数をtaro.nameとして直接参照しているようなコードを書くことができるため、可読性が向上します。

  • 保守性の向上
    インスタンス変数自体はself._nameとして隠蔽されているため、setter以外の方法での代入を防ぐことができます。setterメソッドを消去することで、値を代入できなくすることもできます。

結論

@propertyはクラス内部のインスタンス変数を操作しやすくするために使用されます。

今回は以上になります。



参考:
(1) ゼロから学ぶPython プロパティ
(2) 【Python】propertyとついでにclassを完全に理解する

Discussion