__{get|set}attr__ と似ていて非常に紛らわしい特殊メソッドに "__get__", "__set__", "__del__" がある。
これは「ディスクリプタ(Descriptor)」と呼ばれる特殊なオブジェクトを作る時に実装するメソッドである。
分かりづらい概念で、Python Language Reference (2.5) にも "3.4.2.2 Implementing Descriptors" としてさらっと説明されているだけ。しかも、標準ライブラリでgrepしてもtestコードやinspectモジュールのドキュメント部分くらいしかヒットしないという、「これ本当に使おうと思って実装した機能か?」と疑いたくなるような仕様である。
見た方が早いので、先に __get__() だけ実装したDescriptorを動かしてみる。
>>> class D1(object): # これがディスクリプタになる。 ... def __get__(self, obj, type): ... print repr(self) ... print repr(obj) ... print repr(type) ... print "in __get__()" ... return 'abc' ... >>> class C(object): ... x = D1() # ディスクリプタは、親のオブジェクトがあって初めて意味を持つ。 ... y = 'ABC' ... >>> c = C()
ここでインスタンスcの属性x, yにアクセスしてみる。
>>> c.x <__main__.D1 object at 0x00AF7C70> <__main__.C object at 0x00AF7CD0> <class '__main__.C'> in __get__() 'abc' >>> c.y 'ABC'
属性yの場合はstringオブジェクトがそのまま評価されるが、属性xの場合は "__get__"特殊メソッドが実装されたディスクリプタであるため、"__get__"が実行されている。
ここで、クラス属性としてxを参照してみる。
>>> C.x <__main__.D1 object at 0x00AF7C70> None <class '__main__.C'> in __get__() 'abc'
興味深いことにクラス属性からだと__get__の二番目の引数, obj にNoneが渡ってくる。
これがディスクリプタの面白い点で、 Effective Python Programming 95Pに "descriptors are how 'self' gets inserted
as the first argument of a method" と書かれている機能である。
Python Language Reference(2.5), "3.4.2.3 Invoking Descriptors"にもあるように、クラス属性としてアクセスされるか、インスタンス属性としてアクセスされるかで obj が変わる。
インスタンス属性経由 : type(a).__dict__['x'].__get__(a, type(a)) クラス属性経由 : A.__dict__['x'].__get__(None, A)
ちなみに __get__() だけ実装したのを「Non-data descriptor」、__get__(), __set__()の両方を実装したのを「data descriptor」と呼ぶらしい。
ところで冒頭で「標準ライブラリでgrepしても~」と書いたが、実はC言語で同じ動作原理で実装されているのが staticmethod(), classmethod() らしい。"self" の受け渡しをハイジャックできるディスクリプタの機能を活用している。
Effective Python にclassmethod()をPythonレベルで実装した例が載っている。
こちらでは、staticmethod()をPythonレベルで実装するサンプルを作ってみたので解説する。
>>> class StaticMethod(object): ... def __init__(self, f): ... self.f = f ... def __get__(self, obj, type): ... def newfunc(*args, **kw): ... return self.f(*args, **kw) ... return newfunc ... >>> class D: ... @StaticMethod ... def f(x): ... print x ... >>> D.f(10) 10
class Dから見ていくと、
>>> class D: ... @StaticMethod ... def f(x): ... print x
これは
class D: def f(x): printx f = StaticMethod(f)
となる。StaticMethodクラスのインスタンスに置換される。
次に
D.f(10)
を見ると、まず"D.f"の部分が評価される。これはクラス属性としてアクセスしているので、
D.__dict__['f'].__get__(None, D)
となる。StaticMethodの__get__()は、デコレートしたときの元関数をラップした関数オブジェクトを返している。
def __get__(self, obj, type): def newfunc(*args, **kw): return self.f(*args, **kw) return newfunc
よって、
D.f(10)
は
newfunc(10)
となり、元の関数fがそのまま動く。
・・・という風に、selfをハイジャックしてどうのこうのしたい場合には上手く使えるようだけど、他の使い方が今の時点ではイマイチ思い浮かばない。まぁ、そのうちどこかのライブラリを読んでいる内に出くわすかも知れない。
コメント