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

Author:
   Raymond Hettinger

Contact:
   <python at rcn dot com>


目次
^^^^

* デスクリプタ HowTo ガイド

  * 概要

  * 定義と導入

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

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

  * デスクリプタの例

  * プロパティ

  * 関数とメソッド

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


概要
====

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

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


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

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

The default behavior for attribute access is to get, set, or delete
the attribute from an object’s dictionary.  For instance, "a.x" has a
lookup chain starting with "a.__dict__['x']", then
"type(a).__dict__['x']", and continuing through the base classes of
"type(a)" excluding metaclasses. If the looked-up value is an object
defining one of the descriptor methods, then Python may override the
default behavior and invoke the descriptor method instead. Where this
occurs in the precedence chain depends on which descriptor methods
were defined.  Note that descriptors are only invoked for new style
objects or classes (a class is new style if it inherits from "object"
or "type").

デスクリプタは、強力な、多目的のプロトコルです。これはプロパティ、メソ
ッド、静的メソッド、クラスメソッド、そして "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)" が呼び出されます。

The details of invocation depend on whether "obj" is an object or a
class. Either way, descriptors only work for new style objects and
classes.  A class is new style if it is a subclass of "object".

オブジェクトでは、その機構は "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__()" をオーバーライドすると、自動的なデスクリプタ
  の 呼び出しが行われなくなる

* "__getattribute__()" is only available with new style classes and
  objects

* "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__()" を使用した探索に
戻ります。

Note, in Python 2.2, "super(B, obj).m()" would only invoke "__get__()"
if "m" was a data descriptor.  In Python 2.3, non-data descriptors
also get invoked unless an old-style class is involved.  The
implementation details are in "super_getattro()" in
Objects/typeobject.c.

上述の詳細は、デスクリプタの機構が、 "__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

このプロトコルは単純ですが、ワクワクする可能性も秘めています。ユースケ
ースの中には、あまりに一般的なので個別の関数の呼び出しにまとめられたも
のもあります。プロパティ、束縛および非束縛のメソッド、静的メソッド、そ
してクラスメソッドは、全てデスクリプタプロトコルに基づいています。


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

"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 つはシームレスに組み合わされ
ています。

クラス辞書は、メソッドを関数として保存します。クラス定義内で、メソッド
は、関数を使うのに便利なツール、 "def" や "lambda" を使って書かれます
。標準の関数との唯一の違いは、第一引数がオブジェクトインスタンスのため
に予約されていることです。 Python の慣習では、このインスタンスの参照は
*self* と呼ばれますが、 *this* その他の好きな変数名で呼び出せます。

メソッドの呼び出しをサポートするために、関数の "__get__()" メソッドは
属性アクセス時にメソッドを束縛します。これにより、すべての関数は、それ
が呼び出されたのがオブジェクトかクラスかによって、束縛か非束縛メソッド
を返す非データデスクリプタになります。pure Python では、これはこのよう
にはたらきます:

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

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

   >>> class D(object):
   ...     def f(self, x):
   ...         return x
   ...
   >>> d = D()
   >>> D.__dict__['f']  # Stored internally as a function
   <function f at 0x00C45070>
   >>> D.f              # Get from a class becomes an unbound method
   <unbound method D.f>
   >>> d.f              # Get from an instance becomes a bound method
   <bound method D.f of <__main__.D object at 0x00B18C90>>

この出力は、束縛メソッドと非束縛メソッドは 2 つの異なる型であることを
示しています。 これらは、異なる型として実装することも出来ますが、
Objects/classobject.c における "PyMethod_Type" の実際の C 実装は 1 つ
のオブジェクトで、 "im_self" が *NULL* (C での "None" と同等のもの) に
設定されているかどうかで決まる 2 つの異なる表現を持ってるのです。

同様に、メソッドオブジェクトを呼び出すことの効果も、 "im_self" フィー
ルドに依ります。設定されていれば (束縛を意味し)、期待通り ("im_func"
フィールドに保存されている) 元の関数が、第一引数をインスタンスとして、
呼び出されます。非束縛なら、すべての引数がそのまま元の関数に渡されます
。 "instancemethod_call()" の実際の C 実装は、型チェックがあるため、も
う少しだけ複雑です。


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

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

まとめると、関数は "__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
