一般的に言えば、記述子は「バインディング動作」を持つオブジェクト属性であり、そのアクセス制御は記述子プロトコル メソッドによってオーバーライドされます。これらのメソッドは、__get__()、__set__()、および __delete__() です。これらのメソッドを持つオブジェクトは記述子と呼ばれます。
属性のデフォルトのアクセス制御は、オブジェクトのディクショナリ (__dict__) からの取得 (get)、設定 (set)、および削除 (delete) です。たとえば、a.x の検索順序は、a.__dict__['x']、次に type(a).__dict__['x']、次に type(a) の親クラス (メタクラスを除く) を検索します。見つかった値が記述子である場合、Python は記述子のメソッドを呼び出して、デフォルトのコントロールの動作をオーバーライドします。ルックアップフェーズのどこでこのオーバーライドが行われるかは、どの記述子メソッドが定義されているかによって異なります。記述子は、新しいスタイルのクラス内でのみ機能することに注意してください。新しいスタイルのクラスと古いスタイルのクラスについては、前の章で説明しました。興味がある場合は、前の章を参照してください。新しいスタイルのクラスの最大の特徴は、すべてのクラスが型クラスまたはオブジェクト クラスを継承していることです。
オブジェクト指向プログラミングでは、クラスの属性が相互依存している場合、記述子を使用してコードを記述すると、ロジックを非常に巧妙に編成できます。 Django の ORM では、models.Model の InterField などのフィールドが記述子を通じて機能を実装します。
最初に次の例を見てみましょう:
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- class User(object): def __init__(self, name='两点水', sex='男'): self.sex = sex self.name = name def __get__(self, obj, objtype): print('获取 name 值') return self.name def __set__(self, obj, val): print('设置 name 值') self.name = val class MyClass(object): x = User('两点水', '男') y = 5 if __name__ == '__main__': m = MyClass() print(m.x) print('\n') m.x = '三点水' print(m.x) print('\n') print(m.x) print('\n') print(m.y)
出力結果は次のとおりです:
获取 name 值 两点水 设置 name 值 获取 name 值 三点水 获取 name 值 三点水 5
この例を通して、__get__() と __get__() をよく観察できます。 _ _set__() はこれらのメソッドを呼び出します。
別の古典的な例を見てください
距離は「メートル」または「フィート」の単位で表現できることはわかっています。ここで、距離を表すクラスを定義します。このクラスには、メートルとフィートという 2 つのプロパティがあります。
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- class Meter(object): def __init__(self, value=0.0): self.value = float(value) def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = float(value) class Foot(object): def __get__(self, instance, owner): return instance.meter * 3.2808 def __set__(self, instance, value): instance.meter = float(value) / 3.2808 class Distance(object): meter = Meter() foot = Foot() if __name__ == '__main__': d = Distance() print(d.meter, d.foot) d.meter = 1 print(d.meter, d.foot) d.meter = 2 print(d.meter, d.foot)
出力結果:
0.0 0.0 1.0 3.2808 2.0 6.5616
上記の例では、Distance のインスタンスに値を割り当てる前に、meter と foot がそれぞれのクラスのインスタンス オブジェクトである必要があると考えていますが、出力は数値です。これは、__get__ が役割を果たしているためです。
meter を変更して int に代入しただけですが、foot も変更されています。
記述子オブジェクト (Meter、Foot) は独立して存在できず、別の所有者クラス (Distance) によって保持される必要があります。記述子オブジェクトは、その所有者インスタンスのプロパティ (例では Foot の instance.meter など) にアクセスできます。
次のセクション