Python プロパティとは(デコレーター)

目次

(確認環境:Python 3)

プロパティとは

プロパティ(property)は、クラスのメンバ変数へのアクセスを、メソッド経由で行うようにカプセル化するための仕組みです。

外部からは通常の変数と同じに見えるが、内部ではメソッドとして動作します。

プロパティを使う主な目的

バリデーション setterで値を検証し、不正な値を弾く。例:負の半径を拒否するCircleクラス。
計算値の提供 getterでその都度計算して返す。例:width×heightでareaを返すRectangleクラス。
読み取り専用 getterのみ定義し、setterを書かないことで読み取り専用属性にできる。
後付けの変更 最初は普通の属性で実装し、後からプロパティに変えても呼び出し側のコードは変更不要。

デコレータ記法(@property)のサンプル

デコレータ記法はproperty()関数呼び出しの書き換え(ラップ)です。(シンタックスシュガー:糖衣構文)

class Color:
	__name = None # 内部用の変数は頭に _ をつけるのが慣習

	@property
	def test1(self):
		return self.__name

	@test1.setter
	def test1(self,a):
		self.__name = a

	@test1.deleter
	def test1(self):
		print("abc")

c1 = Color()

c1.test1 = "red" # setterの機能 10行目で値をセット

print(c1.test1) # red   getterの機能 6行目で値を取得

del(c1.test1) # abc  deleterの機能 14行目を実行

4行目は、@propertyがあります。5行目のtest1がプロパティ名になり、かつgetterの宣言になります。
8行目は、@test1.setterがあります。プロパティ名+setterです。setterの宣言になります。
12行目は、@test1.deleterがあります。プロパティ名+deleterです。deleterの宣言になります。

3つのデコレータ

@property getter。値を取得するときに呼ばれる。必須。
@name.setter setter。値をセットするときに呼ばれる。バリデーションを書くのに最適。
@name.deleter deleter。属性を削除するときに呼ばれる。使用頻度は低い。

property()関数を直接呼ぶ記法のサンプル

この書き方は古いスタイルです。

class Color:
	__name = None # 内部用の変数は頭に _ をつけるのが慣習

	def getName(self):
		return self.__name

	def setName(self,a):
		self.__name = a

	def delName(self):
		print("abc")

	p1 = property(getName,setName,delName)

c1 = Color()

c1.p1 = "red" # setterの機能 8行目で値をセット

print(c1.p1) # red   getterの機能 5行目で値を取得

del(c1.p1) # abc deleterの機能 11行目を実行

13行目は、property関数です。

class property(fget=None, fset=None, fdel=None)
  • fgetは、値を読み出す際の処理です。
  • fsetは、値を代入する際の処理です。入力チェック(バリデーション)に最適。
  • fdelはdelで削除された時の処理です。あまり使われません。

1つめの引数はゲッター(getter)です。値を取得するときに呼ばれます。
2つめの引数はセッター(setter)です。値をセットするときに呼ばれます。
3つめの引数はデリーター(deleter)です。属性を削除するときに呼ばれます。

プロパティ・デコレータ・property()関数の関係

概念 種別 役割
プロパティ 機能・概念 属性アクセスをメソッドで制御する仕組み
デコレータ 構文・糖衣構文 property()呼び出しを簡潔に書く記法
property() 組み込み関数 プロパティオブジェクトを生成する実体

プロパティを使う理由:後付の変更のサンプル

最初はシンプルに普通の属性で実装していたとします。

class Person:
    def __init__(self, name):
        self.name = name  # 普通の属性

p = Person("Alice")
print(p.name)  # → Alice

 

後から名前は空文字NGというバリデーションを追加したとします。

class Person:
    def __init__(self, name):
        self.name = name  # setterが呼ばれる

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

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

 

上記のバリデーションを追加した場合でも、呼び出し側(外側)のコードは一切変更不要です。

p = Person("Alice")
print(p.name)   # 変わらず動く
p.name = "Bob"  # 変わらず動く

もしプロパティがなければ、バリデーションを追加するためにp.nameをp.get_name()のようなメソッド呼び出しに変更する必要が出てきます。

プロパティがあるおかげで、内部の実装だけ変えて外側のインターフェースは据え置きにできる、というのが「後付けの変更」の意味です。

関連の記事

Python クラスの仕組みとサンプル
Python クラスの継承の仕組みとサンプル
Python オーバーライドのサンプル

△上に戻る