一般的に言えば、記述子は「バインディング動作」を持つオブジェクト属性であり、そのアクセス制御は記述子プロトコル メソッドによってオーバーライドされます。これらのメソッドは、__get__()、__set__()、および __delete__() です。これらのメソッドを持つオブジェクトは記述子と呼ばれます。
属性のデフォルトのアクセス制御は、オブジェクトの辞書 (__dict__) から属性を取得 (get)、設定 (set)、および削除 (delete) します。たとえば、 a.x の検索順序は、 a.__dict__['x']、次に type(a).__dict__['x']、次に type(a) の親クラス (メタクラスを除く) を検索します。見つかったものが記述子である場合、Python は記述子のメソッドを呼び出して、デフォルトのコントロールの動作をオーバーライドします。ルックアップフェーズのどこでこのオーバーライドが行われるかは、どの記述子メソッドが定義されているかによって異なります。記述子は、新しいスタイルのクラス内でのみ機能することに注意してください。 (新しいスタイルのクラスは、型またはオブジェクトを継承するクラスです)
記述子は強力で広く使用されています。記述子は、プロパティ、インスタンス メソッド、静的メソッド、クラス メソッド、およびスーパーの背後にあるメカニズムです。記述子は、Python 2.2 で導入された新しいスタイルのクラスを実装するために、Python 自体で広く使用されています。記述子は、基礎となる C コードを簡素化し、Python での日常的なプログラミングに柔軟な新しいツール セットを提供します。
記述子プロトコル
オブジェクトが記述子であり、オブジェクト属性として扱われる場合、デフォルトの検索動作をオーバーライドします (非常に重要)。
オブジェクトが __get__ と __set__ の両方を定義している場合、それはデータ記述子と呼ばれます。 __get__ のみを定義する記述子は、非データ記述子と呼ばれます。
データ記述子と非データ記述子の違いは次のとおりです: インスタンスのディクショナリの優先順位に関連し、インスタンス ディクショナリに記述デバイスと同じ名前の属性がある場合、記述子がデータ記述子の場合、データ記述子が最初に使用されます。非データ記述子の場合は、ディクショナリ内の属性が最初に使用されます。
ここで B は非データ記述子であるため、a.name = 'kk' の場合、a.__dict__ に name 属性があり、それに __set__ を設定します
データ記述子は属性へのアクセスに関してインスタンスの辞書よりも高い優先順位を持っているため、a.__dict__ は空です。
記述子の呼び出し
記述子は次のように直接呼び出すことができます: d.__get__(obj)
ただし、より一般的なケースは、プロパティにアクセスしたときに記述子が自動的に呼び出される場合です。たとえば、obj.d は obj の辞書内で d を検索します。d が __get__ メソッドを定義している場合、次の優先規則に従って d.__get__(obj) が呼び出されます。
呼び出しの詳細は、obj がクラスであるかインスタンスであるかによって異なります。さらに、記述子は、新しいスタイルのオブジェクトと新しいスタイルのクラスに対してのみ機能します。オブジェクトを継承するクラスは、新しいスタイルのクラスと呼ばれます。
オブジェクトの場合、メソッド object.__getattribute__() は b.x を type(b).__dict__['x'].__get__(b, type(b)) に変換します。具体的な実装は、この優先順位に基づいています。つまり、データ記述子はインスタンス変数よりも優先され、インスタンス変数は非データ記述子よりも優先され、__getattr__() メソッド (オブジェクトに含まれている場合) の優先順位が最も低くなります。完全な C 言語実装は、Objects/object.c の PyObject_GenericGetAttr() で確認できます。
クラスの場合、メソッド type.__getattribute__() は B.x を B.__dict__['x'].__get__(None, B) に変換します。 Python で記述すると次のようになります:
いくつかの重要なポイント:
注: Python 2.2 では、m が記述子の場合、super(B, obj).m() はメソッド __get__() のみを呼び出します。 Python 2.3 では、(古いスタイルのクラスでない限り) 非データ記述子も呼び出されます。 super_getattro() の実装の詳細は、Objects/typeobject.c, [del] にあります。同等の Python 実装は、Guido のチュートリアル [/del] にあります (翻訳者注: 元の文は削除されており、参照用に保持されています)。
上記は、記述子メカニズムが object、type、および super の __getattribute__() メソッドに実装されていることを示しています。オブジェクトから派生したクラスは、このメカニズムを自動的に継承するか、同様のメカニズムを持つメタクラスを持ちます。同様に、クラスの __getattribute__() メソッドをオーバーライドして、このクラスの記述子の動作をオフにすることができます。
描述器例子
下面的代码中定义了一个资料描述器,每次 get 和 set 都会打印一条消息。重写 __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), 方法(bound和unbound method), 静态方法和类方法都是基于描述器协议的。