デスクリプタ HowTo ガイド
*************************

著者:
   Raymond Hettinger

問い合わせ先:
   <python at rcn dot com>


目次
^^^^

* デスクリプタ HowTo ガイド

  * 概要

  * 定義と導入

  * デスクリプタプロトコル

  * デスクリプタの呼び出し

  * デスクリプタの例

  * プロパティ

  * 関数とメソッド

  * 静的メソッドとクラスメソッド


概要
====

デスクリプタを定義し、プロトコルを要約し、デスクリプタがどのように呼び
出されるか示します。カスタムのデスクリプタや、関数、プロパティ、静的メ
ソッド、クラスメソッドを含む、いくつかの組み込み Python デスクリプタを
考察します。等価な pure Python 版やサンプルアプリケーションを与えるこ
とにより、それぞれがどのように働くかを示します。

デスクリプタについて学ぶことにより、新しいツールセットが使えるようにな
るだけでなく、Python の仕組みや、洗練された設計のアプリケーションにつ
いてのより深い理解が得られます。


定義と導入
==========

一般に、デスクリプタは "束縛動作 (binding behavior)" をもつオブジェク
ト属性で、その属性アクセスが、デスクリプタプロトコルのメソッドによって
オーバーライドされたものです。このメソッドは、 "__get__()",
"__set__()", および "__delete__()" です。これらのメソッドのいずれかが
、オブジェクトに定義されていれば、それはデスクリプタと呼ばれます。

属性アクセスのデフォルトの振る舞いは、オブジェクトの辞書の属性の取得、
設定、削除です。例えば "a.x" は、まず "a.__dict__['x']"、それから
"type(a).__dict__['x']"、さらに "type(a)" のメタクラスを除く基底クラス
へと続くというように探索が連鎖します。見つかった値が、デスクリプタメソ
ッドのいずれかを定義しているオブジェクトなら、Python はそのデフォルト
の振る舞いをオーバーライドし、代わりにデスクリプタメソッドを呼び出しま
す。これがどの連鎖順位で行われるかは、どのデスクリプタメソッドが定義さ
れているかに依ります。

デスクリプタは、強力な、多目的のプロトコルです。これはプロパティ、メソ
ッド、静的メソッド、クラスメソッド、そして "super()" の背後にある機構
です。これはバージョン 2.2 で導入された新スタイルクラスを実装するため
に、Python のいたるところで使われています。デスクリプタは、基幹にある
C コードを簡潔にし、毎日の Python プログラムに、柔軟な新しいツール群を
提供します。


デスクリプタプロトコル
======================

"descr.__get__(self, obj, type=None) --> value"

"descr.__set__(self, obj, value) --> None"

"descr.__delete__(self, obj) --> None"

これで全てです。これらのメソッドのいずれかを定義すれば、オブジェクトは
デスクリプタとみなされ、探索された際のデフォルトの振る舞いをオーバーラ
イドできます。

あるオブジェクトが "__get__()" と "__set__()" の両方を定義していたら、
それはデータデスクリプタとみなされます。 "__get__()" だけを定義してい
るデスクリプタは、非データデスクリプタと呼ばれます (これらは典型的には
メソッドに使われますが、他の使い方も出来ます)。

データデスクリプタと非データデスクリプタでは、オーバーライドがインスタ
ンスの辞書のエントリに関してどのように計算されるかが異なります。インス
タンスの辞書に、データデスクリプタと同名の項目があれば、データデスクリ
プタの方が優先されます。インスタンスの辞書に、非データデスクリプタと同
名の項目があれば、辞書の項目の方が優先されます。

読み出し専用のデータデスクリプタを作るには、 "__get__()" と
"__set__()" の両方を定義し、 "__set__()" が呼び出されたときに
"AttributeError" が送出されるようにしてください。例外を送出する
"__set__()" メソッドをプレースホルダとして定義すれば、データデスクリプ
タにするのに十分です。


デスクリプタの呼び出し
======================

デスクリプタは、メソッド名で直接呼ぶことも出来ます。例えば、
"d.__get__(obj)" です。

または、一般的に、デスクリプタは属性アクセスから自動的に呼び出されます
。例えば、 "obj.d" は "obj" の辞書から "d" を探索します。 "d" がメソッ
ド "__get__()" を定義していたら、以下に列挙する優先順位に従って、
"d.__get__(obj)" が呼び出されます。

呼び出しの詳細は、"obj" がオブジェクトかクラスかに依ります。

オブジェクトでは、その機構は "b.x" を
"type(b).__dict__['x'].__get__(b, type(b))" に変換する
"object.__getattribute__()" にあります。データデスクリプタの優先度はイ
ンスタンス変数より高く、インスタンス変数の優先度は非データデスクリプタ
より高く、(提供されていれば) "__getattr__()" の優先度が最も低いように
実装されています。完全な C 実装は、 Objects/object.c の
"PyObject_GenericGetAttr()" で見つかります。

クラスでは、その機構は "B.x" を "B.__dict__['x'].__get__(None, B)" に
変換する "type.__getattribute__()" にあります。 pure Python では、この
ようになります:

   def __getattribute__(self, key):
       "Emulate type_getattro() in Objects/typeobject.c"
       v = object.__getattribute__(self, key)
       if hasattr(v, '__get__'):
           return v.__get__(None, self)
       return v

憶えておくべき重要な点は:

* デスクリプタは "__getattribute__()" メソッドによって呼び出される

* "__getattribute__()" をオーバーライドすると、自動的なデスクリプタ
  の 呼び出しが行われなくなる

* "object.__getattribute__()" と "type.__getattribute__()" では、
  "__get__()" の呼び出しが異なる。

* データデスクリプタは、必ずインスタンス辞書をオーバーライドする。

* 非データデスクリプタは、インスタンス辞書にオーバーライドされること
  が ある。

"super()" が返したオブジェクトにはさらにデスクリプタを起動するカスタム
"__getattribute__()" メソッドがあります。 "super(B, obj).m()" の呼び出
しは、 "B" に続き基底クラス "A" を見つけるために
"obj.__class__.__mro__" を探索し、 "A.__dict__['m'].__get__(obj, B)"
を返します。デスクリプタではない場合、 "m" が変更されずに返されます。
辞書になければ、  "m" は "object.__getattribute__()" を使用した探索に
戻ります。

実装の詳細は、 Objects/typeobject.c の "super_getattro()" と、 Guido's
Tutorial にある等価な pure Python 版を参照してください。

上述の詳細は、デスクリプタの機構が、 "__getattribute__()" メソッドに埋
めこまれ、 "object", "type", そして "super()" に使われているということ
を表しています。クラスは、 "object" から導出されたとき、または、同じよ
うな機能を提供するメタクラスをもつとき、この機構を継承します。同様に、
"__getattribute__()" をオーバーライドすることで、デスクリプタの呼び出
しを無効にできます。


デスクリプタの例
================

以下のコードは、オブジェクトが取得と設定のたびにメッセージを表示するデ
ータデスクリプタであるようなクラスを生成します。代わりに
"__getattribute__()" をオーバーライドすると、全ての属性に対してこれが
できます。しかし、このデスクリプタは、少数の選ばれた属性を監視するのに
便利です:

   class RevealAccess(object):
       """A data descriptor that sets and returns values
          normally and prints a message logging their access.
       """

       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
   >>> m.y
   5

The protocol is simple and offers exciting possibilities.  Several use
cases are so common that they have been packaged into individual
function calls. Properties, bound methods, static methods, and class
methods are all based on the descriptor protocol.


プロパティ
==========

"property()" を呼び出すことで、属性へアクセスすると関数の呼び出しを引
き起こす、データデスクリプタを簡潔に組み立てられます。シグネチャはこう
です:

   property(fget=None, fset=None, fdel=None, doc=None) -> property attribute

このドキュメントでは、管理された属性 "x" を定義する典型的な使用法を示
します:

   class C(object):
       def getx(self): return self.__x
       def setx(self, value): self.__x = value
       def delx(self): del self.__x
       x = property(getx, setx, delx, "I'm the 'x' property.")

デスクリプタの見地から "property()" がどのように実装されているかを見る
ために、等価な Python 版をここに挙げます:

   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):
           if obj is None:
               return self
           if self.fget is None:
               raise AttributeError("unreadable attribute")
           return self.fget(obj)

       def __set__(self, obj, value):
           if self.fset is None:
               raise AttributeError("can't set attribute")
           self.fset(obj, value)

       def __delete__(self, obj):
           if self.fdel is None:
               raise AttributeError("can't delete attribute")
           self.fdel(obj)

       def getter(self, fget):
           return type(self)(fget, self.fset, self.fdel, self.__doc__)

       def setter(self, fset):
           return type(self)(self.fget, fset, self.fdel, self.__doc__)

       def deleter(self, fdel):
           return type(self)(self.fget, self.fset, fdel, self.__doc__)

組み込みの "property()" 関数は、ユーザインタフェースへの属性アクセスが
与えられ、続く変更がメソッドの介入を要求するときに役立ちます。

例えば、スプレッドシートクラスが、"Cell('b10').value" でセルの値を取得
できるとします。続く改良により、プログラムがアクセスの度にセルの再計算
をすることを要求しました。しかしプログラマは、その属性に直接アクセスす
る既存のクライアントコードに影響を与えたくありません。この解決策は、
property データデスクリプタ内に値属性へのアクセスをラップすることです:

   class Cell(object):
       . . .
       def getvalue(self):
           "Recalculate the cell before returning value"
           self.recalc()
           return self._value
       value = property(getvalue)


関数とメソッド
==============

Python のオブジェクト指向機能は、関数に基づく環境の上に構築されていま
す。非データデスクリプタを使って、この 2 つはシームレスに組み合わされ
ています。

Class dictionaries store methods as functions.  In a class definition,
methods are written using "def" or "lambda", the usual tools for
creating functions.  Methods only differ from regular functions in
that the first argument is reserved for the object instance.  By
Python convention, the instance reference is called *self* but may be
called *this* or any other variable name.

To support method calls, functions include the "__get__()" method for
binding methods during attribute access.  This means that all
functions are non-data descriptors which return bound methods when
they are invoked from an object.  In pure python, it works like this:

   class Function(object):
       . . .
       def __get__(self, obj, objtype=None):
           "Simulate func_descr_get() in Objects/funcobject.c"
           if obj is None:
               return self
           return types.MethodType(self, obj)

インタプリタを起動すると、この関数デスクリプタが実際にどうはたらくかを
見られます:

   >>> class D(object):
   ...     def f(self, x):
   ...         return x
   ...
   >>> d = D()

   # Access through the class dictionary does not invoke __get__.
   # It just returns the underlying function object.
   >>> D.__dict__['f']
   <function D.f at 0x00C45070>

   # Dotted access from a class calls __get__() which just returns
   # the underlying function unchanged.
   >>> D.f
   <function D.f at 0x00C45070>

   # The function has a __qualname__ attribute to support introspection
   >>> D.f.__qualname__
   'D.f'

   # Dotted access from an instance calls __get__() which returns the
   # function wrapped in a bound method object
   >>> d.f
   <bound method D.f of <__main__.D object at 0x00B18C90>>

   # Internally, the bound method stores the underlying function,
   # the bound instance, and the class of the bound instance.
   >>> d.f.__func__
   <function D.f at 0x1012e5ae8>
   >>> d.f.__self__
   <__main__.D object at 0x1012e1f98>
   >>> d.f.__class__
   <class 'method'>


静的メソッドとクラスメソッド
============================

非データデスクリプタは、関数をメソッドに束縛する、各種の一般的なパター
ンに、単純な機構を提供します。

まとめると、関数は "__get__()" メソッドを持ち、属性としてアクセスされ
たとき、メソッドに変換されます。この非データディスクリプタは、
"obj.f(*args)" の呼び出しを "f(obj, *args)" に変換します。
"klass.f(*args)" を呼び出すと "f(*args)" になります。

このチャートは、束縛と、その 2 つの異なる便利な形をまとめています:

   +-------------------+------------------------+--------------------+
   | 変換              | オブジェクトから呼び出 | クラスから呼び出さ |
   |                   | される                 | れる               |
   |===================|========================|====================|
   | function          | f(obj, *args)          | f(*args)           |
   +-------------------+------------------------+--------------------+
   | 静的メソッド      | f(*args)               | f(*args)           |
   +-------------------+------------------------+--------------------+
   | クラスメソッド    | f(type(obj), *args)    | f(klass, *args)    |
   +-------------------+------------------------+--------------------+

静的メソッドは、下にある関数をそのまま返します。 "c.f" や "C.f" は、
"object.__getattribute__(c, "f")" や "object.__getattribute__(C, "f")"
を直接探索するのと同じです。結果として、関数はオブジェクトとクラスから
同じようにアクセスできます。

静的メソッドにすると良いのは、 "self" 変数への参照を持たないメソッドで
す。

例えば、統計パッケージに、実験データのコンテナがあるとします。そのクラ
スは、平均、メジアン、その他の、データに依る記述統計を計算する標準メソ
ッドを提供します。しかし、概念上は関係があっても、データには依らないよ
うな便利な関数もあります。例えば、 "erf(x)" は統計上の便利な変換ルーチ
ンですが、特定のデータセットに直接には依存しません。これは、オブジェク
トからでもクラスからでも呼び出せます: "s.erf(1.5) --> .9332" または
"Sample.erf(1.5) --> .9332" 。

静的メソッドは下にある関数をそのまま返すので、呼び出しの例は面白くあり
ません:

   >>> class E(object):
   ...     def f(x):
   ...         print(x)
   ...     f = staticmethod(f)
   ...
   >>> print(E.f(3))
   3
   >>> print(E().f(3))
   3

非データデスクリプタプロトコルを使うと、pure Python 版の
"staticmethod()" は以下のようになります:

   class StaticMethod(object):
       "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

       def __init__(self, f):
           self.f = f

       def __get__(self, obj, objtype=None):
           return self.f

静的メソッドとは違って、クラスメソッドは関数を呼び出す前にクラス参照を
引数リストの先頭に加えます。このフォーマットは、呼び出し元がオブジェク
トでもクラスでも同じです:

   >>> class E(object):
   ...     def f(klass, x):
   ...         return klass.__name__, x
   ...     f = classmethod(f)
   ...
   >>> print(E.f(3))
   ('E', 3)
   >>> print(E().f(3))
   ('E', 3)

この振る舞いは、関数がクラス参照のみを必要とし、下にあるデータを考慮し
ないときに便利です。クラスメソッドの使い方の一つは、代わりのクラスコン
ストラクタを作ることです。Python 2.3 では、クラスメソッド
"dict.fromkeys()" は新しい辞書をキーのリストから生成します。等価な
pure Python 版は:

   class Dict(object):
       . . .
       def fromkeys(klass, iterable, value=None):
           "Emulate dict_fromkeys() in Objects/dictobject.c"
           d = klass()
           for key in iterable:
               d[key] = value
           return d
       fromkeys = classmethod(fromkeys)

これで一意なキーを持つ新しい辞書が以下のように構成できます:

   >>> Dict.fromkeys('abracadabra')
   {'a': None, 'r': None, 'b': None, 'c': None, 'd': None}

非データデスクリプタプロトコルを使った、 "classmethod()" の pure
Python 版はこのようになります:

   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
