ディスクリプタ
ディスクリプタとは
代入、参照、削除を扱う属性を持ったオブジェクトをディスクリプタという。具体的には、__get__、__set__、__delete__のいずれかを定義したオブジェクトをディスクリプタという。
ディスクリプタにより属性のデフォルトlookupの挙動を変更することができる。property、bound method、unbound method、static method、class method、superもディスクリプタの考え方でC言語で実装されている。
シグネチャ
__get__、__set__両方定義されているオブジェクトをdata descriptorという。
__get__のみ定義されているオブジェクトをnon-data descriptorという。
read-onlyにするには、__get__と__set__を両方定義して、__set__でAttributeError例外を送出すれば良い。
ディスクリプタの実行
d.__get__(obj) により直接実行できる。もし d が__get__メソッドを定義していれば、通常の属性アクセス obj.d により 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) に変換されて実行される。
覚えておくべき重要なポイントは、
ディスクリプタの例
>>> 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
参考文献:
- How-To Guide for Descriptors (http://users.rcn.com/python/download/Descriptor.htm)