ディスクリプタとプロパティ

昨日のエキPy読書会でをお勉強しました。

ディスクリプタとはオブジェクトの属性が参照されたときの動作をカスタマイズするためのものです。特に__get__と__set__を両方持つものをデータディスクリプタというらしいです。

このディスクリプタはクラスの属性として設定してあげると、=で代入するときは__set__が呼ばれ、参照するときは__get__が呼ばれるようです。サンプルをエキスパートPythonプログラミングから抜粋して載っけときます。

class UppserString(object):
    def __init__(self):
        self._val = ''
    def __get__(self):
        return self._val
    def __set__(self, val):
        self._val = val.upper()

class MyClass(object):
    attr = UperString()

このディスクリプタの考えを基にして作られているのがプロパティ。シンタックスシュガー的なものかな?

このプロパティの書き方は一昔前までは、下記のように書いていたようです。

class MyClass(object):
    def __init__(self):
        self._val = ''
    def fget(self):
        return self._val
    def fset(self, val):
        self._val = val.upper()

    attr = property(fget, fset)

デコレータができてからはでこれたーでやりたいよねってことで、下記のようになったらしいです。

class MyClass(object):
    def __init__(self):
        self._val = ''

    @property
    def attr(self):
        return self._val
    
    @attr.setter
    def attr(self, val):
        self._val = val.upper()

最近はほとんど上記の書き方のようですが、マニアックな人は下記のような書き方をするそうです。

class MyClass(object):
    def __init__(self):
        self._val = ''

    @apply
    def attr():
        def fget(self):
            return self._val
        def fset(self, val):
            self._val = val.upper()
        return property(fget, fset)

僕はこの書き方が一番気に入ったのですが、applyってなんだよとかいまいち人気がないみたいです。
親クラスの名前空間汚さないし、プロパティで処理がまとまってるし綺麗だと思ったんですけどね。
@propertyの方だと、定義順番が縛られるのがいやだーwリファクタリングのときとか下手に移動できなさそう。自分はいいけど後で読む人とか改変する人とかちょっと困らないかな〜と。

で、@applyの方をできるだけ気持ち悪くないようにしてみたんですが、どうでしょうか?逆に気持ち悪いかも?w

property, propgen = apply, property

class Sample(object):
    def __init__(self):
        self._val = 0
    
    @property
    def x():
        doc = "this is property"
        def fget(self):
            return self._val
        def fset(self, val):
            self._val = val * val
        def fdel(self):
            self._val = 0
        
        return propgen(**locals())

print Sample.x.__doc__
s = Sample()
print s.x
s.x = 5
print s.x
del s.x
print s.x

[注] 20101119 propertyのgetter,setterのメソッド名を__get__->fget,__set__->fsetに変更