オブジェクトの属性を操る(3) - property(プロパティ)

【 目次 】

プロパティはjavaで言うgetter, setterのようなもの。
アクセサと言ったりもする。

javaの開発現場では、インスタンス変数をクラスの外部からアクセスする時に直接インスタンス変数にアクセスするのではなく、必ずgetter, setterを使うようにと教えられるところも多いのだが、...

まえおきはここまでにして、
pythonのプロパティには他の言語には無いpython独自のものがいろいろと...

組込み関数propertyによるプロパティの定義

プロパティを定義するには組み込み関数propertyを使う。

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)

と等価のようだ。

プロパティの機能は実はディスクリプタを使って実装されている。
これについては「プロパティとディスクリプタ」で述べる事にする。

ページのトップへ戻る