組み込み関数 property
がどのように動作するかを理解したいと思います。混乱しているのは、property
はデコレーターとしても使えるのですが、引数を取るのは組み込み関数として使ったときだけで、デコレーターとして使ったときは取らないということです。
この例はドキュメントからの引用です。
class C(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.")
property
'の引数は getx
, setx
, delx
と doc 文字列です。
以下のコードでは、property
をデコレータとして使用しています。その対象となるのは x
関数ですが、上のコードでは引数にオブジェクト関数を入れる場所がありません。
class C(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
また、x.setter
とx.deleter
のデコレーターはどのように作られるのでしょうか?
混乱しています。
関数property()
は,特殊な記述子オブジェクトを返します.
>>> property()
<property object at 0x10ff07940>
このオブジェクトには,追加のメソッドがあります.
>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>
これらはデコレーターとしても機能します。これらは新しいプロパティオブジェクトを返します。
>>> property().getter(None)
<property object at 0x10ff079f0>
を返します。これは古いオブジェクトのコピーですが、関数のひとつが置き換えられています。
デコレータ`構文は単なる構文上の糖であることを覚えておいてください。
@property
def foo(self): return self._foo
と同じ意味になります。
def foo(self): return self._foo
foo = property(foo)
つまり、関数である foo
は property(foo)
に置き換えられ、上で見たように特別なオブジェクトになります。次に @foo.setter()
を使うと、上で紹介した property().setter
メソッドを呼び出して、プロパティの新しいコピーを返しますが、今回はセッター関数が装飾されたメソッドに置き換えられています。
次のシーケンスでも、デコレータメソッドを使用して完全なプロパティを作成しています。
まず、いくつかの関数と、ゲッターだけの property
オブジェクトを作成します。
>>> def getter(self): print('Get!')
...
>>> def setter(self, value): print('Set to {!r}!'.format(value))
...
>>> def deleter(self): print('Delete!')
...
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True
次に、.setter()
メソッドを使ってセッターを追加します。
>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True
最後に.deleter()
メソッドで削除子を追加します。
>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True
最後になりましたが、property
オブジェクトは descriptor object として動作しますので、インスタンス属性の取得、設定、削除にフックする .__get__()
, .__set__()
, .__delete__()
メソッドを持っています。
>>> class Foo: pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!
Descriptor Howto には,property()
型の pure Python サンプル実装 が含まれています.
クラス Property: "Objects/descrobject.c で PyProperty_Type() をエミュレートする"
def init(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fget = fget > self.fset = fset self.fget = fget > self.fset = fset > self.fdel = fdel doc が None で、fget が None でない場合。 doc = fget.doc self.doc = doc
def get(self, obj, objtype=None): objがNoneの場合。 return self if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(obj)
def set(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(obj, value)
def delete(self, obj): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(obj)
def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.doc)
def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.doc)
def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.doc)
ドキュメントによると読み取り専用のプロパティを作成するための単なる近道だそうです。そのため
@property
def x(self):
return self._x
と同等です。
def getx(self):
return self._x
x = property(getx)
最初の部分は簡単です。
@property
def x(self): ...
と同じです。
def x(self): ...
x = property(x)
property
を作成するための簡略化された構文になります。次のステップは、このプロパティをセッターとデリッターで拡張することです。これには適切なメソッドが必要です。
@x.setter
def x(self, value): ...
は、古い x
からすべてを継承し、さらに与えられたセッターを加えた新しいプロパティを返します。
x.deleter` も同じように動作します。