ディスクリプタ

ディスクリプタとは

代入、参照、削除を扱う属性を持ったオブジェクトをディスクリプタという。具体的には、__get__、__set__、__delete__のいずれかを定義したオブジェクトをディスクリプタという。

ディスクリプタにより属性のデフォルトlookupの挙動を変更することができる。property、bound method、unbound method、static method、class method、superもディスクリプタの考え方でC言語で実装されている。

シグネチャ

  • __get__(self, obj, type=None) ⇒ value
  • __set__(self, obj, value) ⇒ None
  • __delete__(self, obj) ⇒ None


__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) に変換されて実行される。


覚えておくべき重要なポイントは、

  • ディスクリプタは、__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


参考文献: