今回は、Pythonのプロパティ・デコレーターについて見ていきましょう。
Pythonにはデコレーターという非常に便利な機能があり、これは関数ラッパーの構文上の糖分に過ぎません。
ここでは、デコレータの特殊なタイプであるプロパティ・デコレータに焦点を当てます。
この話題は少しわかりにくいかもしれませんので、例を挙げながら順を追って説明します。
それでは、始めましょう。
Python Property Decorator は何に基づいてるんですか?
Property デコレータは、組み込みの property()
関数に基づいています。
この関数は、特別な property
オブジェクトを返します。
Python インタープリターでこれを呼び出して、見てみましょう。
>>> property ()
< property object at 0x000002BBD7302BD8 >
|
この property
オブジェクトは、オブジェクトの値を取得したり設定したりするための、いくつかの特別なメソッドを持っています。
また、このオブジェクトを削除するためのメソッドも持っています。
メソッドの一覧は以下の通りです。
-
property().getter
(プロパティゲッター -
property().setter
(プロパティセッター) -
property().deleter
(プロパティ().セッター)
しかし、これだけでは終わりません! これらのメソッドは他のオブジェクトでも使用することができ、それ自体がデコレータとして機能します!
例えば、 property().getter(obj)
を使用すると、別のプロパティオブジェクトを取得することができます!
つまり、property デコレータはこの関数を使用し、オブジェクトへの読み書きのための特別なメソッドを持っているということです。
では、見てみましょう。
Python プロパティデコレータを使用する
プロパティデコレータを使うには、関数やメソッドにラップする必要があります。
以下は簡単な例です。
@property def fun(a, b):
return a + b
|
と同じです。
def fun(a, b):
return a + b
fun = property (fun)
|
つまり、ここでは property()
を fun()
のまわりにくるんでいるわけです。
それでは、クラスメソッドにプロパティ・デコレータを適用した簡単な例を見てみましょう。
次のようなクラスで、デコレータを使用していないメソッドを考えてみましょう。
class Student():
def __init__( self , name, id ):
self .name = name
self . id = id
self .full_id = self .name + " - " + str ( self . id )
def get_name( self ):
return self .name
s = Student( "Amit" , 10 )
print (s.name)
print (s.full_id)
# Change only the name s.name = "Rahul"
print (s.name)
print (s.full_id)
|
結果は以下の通りです。
Amit Amit - 10 Rahul Amit - 10 |
ここで、オブジェクトの name
属性を変更しただけでは、 full_id
属性への参照はまだ更新されていません!
nameや
idが更新されたときに
full_id属性も更新されるようにするには、
full_id` をメソッドにすることで解決できます。
class Student():
def __init__( self , name, id ):
self .name = name
self . id = id
def get_name( self ):
return self .name
# Convert full_id into a method
def full_id( self ):
return self .name + " - " + str ( self . id )
s = Student( "Amit" , 10 )
print (s.name)
# Method call print (s.full_id())
s.name = "Rahul"
print (s.name)
# Method call print (s.full_id())
|
結果は以下の通りです。
Amit Amit - 10 Rahul Rahul - 10 |
ここでは、 full_id
をメソッド full_id()
に変換することで、問題を解決しました。
しかし、これはこの問題に取り組むベストな方法ではありません。
なぜなら、このような属性をすべてメソッドに変換し、属性をメソッド呼び出しに変更する必要がある場合があるからです。
この問題を解決するために、代わりに @property
デコレータを使用することができます。
このアイデアは、 full_id()
をメソッド化し、それを @property
で囲むことです。
この方法では、関数の呼び出しとして扱うことなく、 full_id
を更新することができます。
これを直接行うには、 s.full_id
とします。
ここで、メソッドの呼び出しがないことに注目してください。
これは、プロパティデコレータを使用しているためです。
では、実際に試してみましょう。
class Student():
def __init__( self , name, id ):
self .name = name
self . id = id
def get_name( self ):
return self .name
@property
def full_id( self ):
return self .name + " - " + str ( self . id )
s = Student( "Amit" , 10 )
print (s.name)
# No more method calls! print (s.full_id)
s.name = "Rahul"
print (s.name)
# No more method calls! print (s.full_id)
|
結果は以下の通りです。
Amit Amit - 10 Rahul Rahul - 10 |
確かに、これでうまくいきました! これで、括弧を使って full_id
を呼び出す必要がなくなりました。
これはまだメソッドですが、プロパティデコレーターはそれを隠して、クラスのプロパティであるかのように扱います! これで名前が意味をなすようになったでしょう!
この記事もチェック:Pythonのデコレーターとは?使い方や戻り値などを分かりやすく解説する
プロパティをセッターで使用する
上記の例では、full_id
プロパティを明示的に直接変更しなかったので、このアプローチはうまくいきました。
デフォルトでは、 @property
を使用すると、そのプロパティは読み取り専用になるだけです。
つまり、そのプロパティを明示的に変更することはできません。
s.full_id = "Kishore"
print (s.full_id)
|
結果は以下の通りです。
- - - > 21 s.full_id = "Kishore"
22 print (s.full_id)
AttributeError: can't set attribute
|
明らかに、このプロパティは読み取り専用なので、私たちは権限を持っていません!
このプロパティを書き込み可能にするには、先ほど説明した property().setter
メソッド (これはデコレーターでもあります) を思い出してください。
このメソッドはデコレーターでもあります。
そして、@full_id.setter
を使用して、もうひとつの full_id
プロパティを追加して書き込み可能にできることがわかりました。
この @full_id.setter
は元の full_id
プロパティをすべて継承しているので、直接追加することができます!しかし、 @full_id.setter
を直接使用することはできません。
しかし、セッターのプロパティで full_id
プロパティを直接使用することはできません。
これは無限の再帰的降下を引き起こすことになります。
class Student():
def __init__( self , name, id ):
self .name = name
self . id = id
def get_name( self ):
return self .name
@property
def full_id( self ):
return self .name + " - " + str ( self . id )
@full_id .setter
def full_id( self , value):
# Infinite recursion depth!
# Notice that you're calling the setter property of full_id() again and again
self .full_id = value
|
これを避けるために、隠し属性 _full_id
をクラスに追加します。
代わりにこの属性を返すように @property
デコレーターを変更します。
更新されたコードは次のようになります。
class Student():
def __init__( self , name, id ):
self .name = name
self . id = id
self ._full_id = self .name + " - " + str ( self . id )
def get_name( self ):
return self .name
@property
def full_id( self ):
return self ._full_id
@full_id .setter
def full_id( self , value):
self ._full_id = value
s = Student( "Amit" , 10 )
print (s.name)
print (s.full_id)
s.name = "Rahul"
print (s.name)
print (s.full_id)
s.full_id = "Kishore - 20"
print (s.name)
print (s. id )
print (s.full_id)
|
結果は以下の通りです。
Amit Amit - 10 Rahul Amit - 10 Rahul 10 Kishore - 20 |
これで、full_id
プロパティにゲッターとセッターの属性を持たせることに成功しました!
まとめ
これで、property デコレータについて、また、なぜ _full_id
のようなクラス属性を使用する必要があるのかを理解していただけたかと思います。
これらの隠されたクラス属性 (_full_id
など) を使用すると、外側の full_id
プロパティを簡単に使用することができます!これは、現代のオープンソースでプロパティが多用されている理由です。
これこそが、最近のオープンソースプロジェクトでプロパティが多用される理由です。
エンドユーザーにとって非常に使いやすく、開発者にとっても隠し属性とそうでない属性を簡単に切り分けることができるのです!
参考文献
- StackOverflow プロパティデコレータに関する質問