オブジェクトの属性を操る(3) - property(プロパティ)
【 目次 】
プロパティはjavaで言うgetter, setterのようなもの。
アクセサと言ったりもする。
javaの開発現場では、インスタンス変数をクラスの外部からアクセスする時に直接インスタンス変数にアクセスするのではなく、必ずgetter, setterを使うようにと教えられるところも多いのだが、...
まえおきはここまでにして、
pythonのプロパティには他の言語には無いpython独自のものがいろいろと...
組込み関数propertyによるプロパティの定義
プロパティを定義するには組み込み関数propertyを使う。
- 2. 組み込み関数 — Python 2.7ja1 documentation
property([fget[, fset[, fdel[, doc]]]])
...
fget は属性値を取得するための関数で、同様に fset は設定するための関数、 fdel は削除するための関数です。
pythonの場合、getter,setterの他に、他の言語には無いdeleterが定義できる。
単純にプライベート変数として定義した変数_xをラップしたプロパティxを定義する例を以下に示す。
class MyCls(object): def __init__(self): self._x = None def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.")
定義したプロパティを使ってみる。
my_inst=MyCls() my_inst.x=10 print my_inst.x
実行結果
10
deleterを使ってみる。
del(my_inst.x) print my_inst.x
実行結果
Traceback (most recent call last): ... my_inst.x=10 AttributeError: can't set attribute
プロパティを削除したのでアクセスできずにエラーが発生。
読取り専用のプロパティ
プロパティの定義においてgetter以外は省略可能で省略した場合は読取り専用のプロパティになる。
class MyCls(object): def __init__(self): self._x = 10 def getx(self): return self._x x = property(getx)
デコレータによるプロパティの定義
プロパティはデコレータを使って記述する事もできる。
class MyCls(object): def __init__(self): self._x = None @property def x(self): """I'm the 'x' property.""" return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x
デコレータを使って記述する場合は@propertyデコレータは必須で、@propertyの後のメソッドにgetterのコードを記述する。
組込み関数によるプロパティの定義と同様、getter以外は省略可能である。
省略した場合は読取り専用のプロパティという事になる。
@propertyを指定したあとダミーの関数を指定して@x.getterによりgetterを再指定する事もできる。
class MyCls(object): def __init__(self): self._x = None @property def x(self): pass @x.getter def x(self): return self._x
この場合@propertyは属性xがプロパティである事を示し、@x.getterによりxのgetterを指定する役割を果たすことになる。
書き込み専用のプロパティ
書き込み専用のプロパティはどのように記述したら良いだろう?
組込み関数を使って記述する場合は名前付き引数を使って
class MyCls(object): def setx(self, value): self._x = value x = property(fset=setx)
デコレータを使って記述する場合は@propertyの後のgetter関数の記述を省略できないので、以下のようにダミーのgetter関数を余分に記述してAttributeErrorを送出してやればいいのかな?
class MyCls(object): @property def x(self): raise AttributeError @x.setter def x(self, value): self._x = value
propertyはオブジェクト
type関数を使ってプロパティにより実装した属性xの型を調べてみる。
プロパティはメソッドと同様、クラスの属性となるので
print type(MyCls.x)
実行結果
<type 'property'>
プロパティはpropertyクラスのオブジェクトとなる。
では、dir関数を使ってMyCls.xから何がアクセスできるか調べてみると、
print dir(MyCls.x)
実行結果
['__class__', '__delattr__', '__delete__', '__doc__', '__format__', '__get__', ' __getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex __', '__repr__', '__set__', '__setattr__', '__sizeof__', '__str__', '__subclassh ook__', 'deleter', 'fdel', 'fget', 'fset', 'getter', 'setter']
MyCls.xは特殊フィールドの他に'deleter', 'fdel', 'fget', 'fset', 'getter', 'setter'にアクセスできる事になる。
そして、fsetの型を調べてみると
print type(MyCls.x.fset)
実行結果
<type 'function'>
関数オブジェクトが格納されていることがわかる。
という事は
my_inst.x="xxx"
によるgetterへのアクセスは、実は、クラス経由でインスタンスメソッドにアクセスするとすると
MyCls.x.fset(my_inst, "xxx")
を実行していることになるようだ。
試しに、これを実行後にxの値を表示してみると
print my_inst.x
実行結果
xxx
プロパティの値が変更されているのが確認できる。
同様に、getterによる動作は
print MyCls.x.fget(my_inst)
と等価のようだ。
プロパティの機能は実はディスクリプタを使って実装されている。
これについては「プロパティとディスクリプタ」で述べる事にする。