概要
Pythonのクラスでpropertyデコレータを使うとどうしてメソッド呼び出しをせずに値が取れるのか調べた。
実験
環境
Pythonのバージョン
Python 3.5.2
使ったコード
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
#print(obj)
#print(objtype)
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
class Hoge:
def __init__(self):
self._foo = 'foo!'
@Property
def foo(self):
return self._foo
if __name__ == '__main__':
h = Hoge()
print(h.foo)
Propertyクラスの実装はhttps://docs.python.jp/3/howto/descriptor.html#properties のもので、デバッグ用にprintを追加し、__init__
と__get__
以外のメソッドを削った。
実行すると以下のようになる。
$ python property.py
foo!
printのコメントアウトを外してみる
コメントアウトを外して実行すると下のような結果になり、どうにかこうにかHoge
が渡されてきているのが分かる。
$ python property.py
<__main__.Hoge object at 0x00000280356C87F0>
<class '__main__.Hoge'>
foo!
@Property
を消してみる
$ python property.py
<bound method Hoge.foo of <__main__.Hoge object at 0x000002209BB687B8>>
メソッドは当然実行されていない。
print(h.foo) => print(h.foo())
に変更すると、
$ python property.py
foo!
元と同じ出力になる。
考察
https://docs.python.jp/3/reference/compound_stmts.html#function によると、
@Property
def foo(self):
return self._foo
は、
def foo(self):
return self._foo
foo = Property(foo)
とほぼ等価らしい。
これでPropertyクラスのfgetにfooが入る。
https://docs.python.jp/3/howto/descriptor.html#invoking-descriptors によると、呼び出し側の h.foo
はtype(h).__dict__['foo'].__get__(h, type(h))
にobject.__getattribute__
によって変換されるらしい。
type(h).__dict__['foo']
は上のデコレータでPropertyのインスタンスを代入したのと同じなので、__get__()
が、obj=h, objtype=type(h)を引数として呼び出される。
こうしてfooメソッドにselfを渡して実行することができ、その結果が値として返ってくるのでprint(h.foo)
で値を表示できた。