ディスクリプタとは代入、参照、削除を扱う属性を持ったオブジェクトのことをいう。
具体的には、__get__、__set__、__delete__のいずれかを定義したオブジェクトをディスクリプタという。
ディスクリプタにより属性のデフォルト lookup の挙動を変更することができる。
property、bound method、unbound method、static method、class method、super もディスクリプタの考え方でC言語で実装されている。
もし d が__get__メソッドを定義していれば、通常の属性アクセス obj.d により d.__get__(obj) が自動的に実行される。
d.__get__(obj) により直接実行することも可能。
ディスクリプタ実行の詳細は obj がオブジェクトであるかクラスであるかによって異なる。
- オブジェクトの場合
- object.__getattribute__の中で b.x は、type(b).__dict__['x'].__get__(b, type(b)) に変換されて実行される。
- 優先順位は高い順から、data descriptor、instance variable、non-data descriptor、__getattr__となる。
- クラスの場合
- type.__getattribute__の中で、 B.x は、 B.__dict__['x'].__get__(None, B) に変換されて実行される。
覚えておくべき重要なポイントは、
- ディスクリプタは、__getattribute__メソッドにより実行される
- __getattribute__をオーバーライドするとディスクリプタが実行されない
- __getattribute__は新スタイルクラスとそのインスタンスのみで有効
- object.__getattribute__とtype.__getattribute__は違った__get__を呼び出す
- data descriptorは常にインスタンスオブジェクトの属性参照用の辞書をオーバーライドする
- non-data descriptorはインスタンスオブジェクトの属性参照用の辞書にオーバーライドされとは限らない
ディスクリプタの例
>>> class RevealAccess(object):
... def __init__(self, initval=None, name='var'):
... self.val = initval
... self.name = name
... def __get__(self, obj, objtype):
... print 'Retrieving', self.name
... return self.val
... def __set__(self, obj, val):
... print 'Updating', self.name
... self.val = val
...
>>> class MyClass(object):
... x = RevealAccess(10, 'var "x"')
... y = 5
...
>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
クラスメソッドの実装例
>>> class ClassMethod(object):
... "Emulate PyClassMethod_Type() in Objects/funcobject.c"
...
... def __init__(self, f):
... self.f = f
...
... def __get__(self, obj, klass=None):
... if klass is None:
... klass = type(obj)
... def newfunc(*args):
... return self.f(klass, *args)
... return newfunc
...
>>> class D:
... @ClassMethod
... def f(cls, x):
... print x
...
>>> d = D()
>>> d.f(1)
1
>>> D.f(1)
1