Pythonディスクリプタの`__get__`、`__set__`、`__delete__`プロトコルによる解説
Wenhao Wang
Dev Intern · Leapcell

はじめに
Pythonの世界では、属性へのアクセスやメソッドの呼び出しといった、一見単純な操作の多くは、強力でありながらしばしば過小評価されているメカニズム、すなわちディスクリプタプロトコルによって支えられています。ディスクリプタは、Pythonがオブジェクト属性の扱いにこれほどまでに動的で柔軟であることを可能にする、静かなる設計者です。
@propertyデコレータがどのように機能するのか、あるいはアンバウンドメソッドやバウンドメソッドがどのようにself引数を管理するのか、疑問に思ったことはありませんか? その答えは、しばしばディスクリプタにたどり着きます。このプロトコルを理解することは、単にPythonの内部を解明するだけでなく、より表現力豊かで、堅牢で、Pythonicなコードを書く能力を獲得することでもあります。この記事では、__get__、__set__、__delete__という基本的なメソッドを探求し、その原理、実装、および実用的な応用例を示すことで、Pythonディスクリプタの謎を解き明かしていきます。
ディスクリプタの理解
コアプロトコルに飛び込む前に、ディスクリプタが何であるかを確立しましょう。
Pythonでは、__get__、__set__、または__delete__メソッドのいずれかを実装しているオブジェクトをディスクリプタと呼びます。これらのメソッドは特別です。なぜなら、これらのメソッドを持つオブジェクトがクラス属性に割り当てられると、Pythonは、そのクラスのインスタンスに対する属性アクセスを、ディスクリプタのメソッドに委譲するからです。この委譲は、ディスクリプタプロトコルの基盤です。
ディスクリプタには、主に2つのタイプがあります。
- データディスクリプタ:
__get__と__set__の両方を定義しているオブジェクト。これらは読み書き両方ができるため、「データ」と呼ばれます。 - 非データディスクリプタ:
__get__のみを定義しているオブジェクト。これらは主に取得のために使用され、インスタンスの辞書内の属性値を直接変更することはできません。
データディスクリプタと非データディスクリプタの区別は、属性のルックアップ順序に影響するため重要です。データディスクリプタはインスタンス辞書よりも優先されますが、非データディスクリプタはインスタンス辞書によって上書きされる可能性があります。
それでは、ディスクリプタプロトコルの3つの柱である__get__、__set__、__delete__を探求しましょう。
__get__メソッド
__get__メソッドは、属性にアクセスされたときに呼び出されます。そのシグネチャは通常__get__(self, instance, owner)であり、そこでは:
self: ディスクリプタインスタンス自体。instance: 属性にアクセスされたクラスのインスタンス。属性がクラスから直接(例:MyClass.attribute)アクセスされた場合、instanceはNoneになります。owner: ディスクリプタを所有するクラス(例:MyClass)。
例で示しましょう:
class MyDescriptor: def __init__(self, value=None): self._value = value def __get__(self, instance, owner): if instance is None: print(f