これは、言語と実装に大きく依存します。CPythonとPyPyについて私が知っていることをお話しします。
CPythonがほとんどの部分で行うことでもある一般的な考え方は、次のようになります。
- すべてのオブジェクトにはクラスがあり、具体的にはそのクラスオブジェクトへの参照があります。
- 明らかに個々のオブジェクトに格納されているインスタンスメンバーとは別に、クラスにはメンバーもあります。これにはメソッドが含まれるため、メソッドにはオブジェクトごとのコストはありません。
- クラスには、継承関係によって決定されるメソッド解決順序(MRO)があり、各基本クラスは1回だけ発生します。多重継承がない場合、これは単に基本クラスへの参照になりますが、この方法では、MROをその場で把握するのは困難です(毎回最も派生したクラスから開始する必要があります)。
- (クラスもオブジェクトであり、クラス自体がありますが、ここではそれについて詳しく説明します。)
- オブジェクトの属性ルックアップが失敗した場合、同じ属性がMROで指定された順序でMROのクラスでルックアップされます。(これはデフォルトの動作であり、
__getattr__
やなどの魔法のメソッドを定義することで変更できます__getattribute__
。)
これまでのところ単純であり、バインドされたメソッドの説明ではありません。同じことを話していることを確認したかっただけです。欠落している部分は記述子です。記述子プロトコルは、言語リファレンスの「ディープマジック」セクションで定義されていますが、短くて単純な話は、クラスのルックアップは、メソッドを介して結果として生じるオブジェクトによってハイジャックされる可能性があるということです。__get__
さらに重要なことに、この__get__
メソッドは、ルックアップがインスタンスで開始されたか、「所有者」(クラス)で開始されたかを通知されます。
UnboundMethod
Python 2には、 (メソッドを除いて__get__
)単純に関数をラップして、許容可能なタイプでないClass.method(self)
場合にエラーをスローする、醜く不要な記述子があります。self
Python 3では、__get__
はすべての関数オブジェクトの一部であり、バインドされていないメソッドはなくなりました。どちらの場合も、__get__
メソッドはクラスで検索するとそれ自体を返し(したがって、を使用できますClass.method
。これはいくつかの場合に役立ちます)、オブジェクトで検索すると「バインドされたメソッド」オブジェクトを返します。このバインドされたメソッドオブジェクトは、生の関数とインスタンスを格納し、後者を最初の引数として前者に渡すだけです__call__
(関数呼び出し構文をオーバーライドする特別なメソッド)。
したがって、CPythonの場合:メソッドをバインドするにはコストがかかりますが、思ったよりも小さくなります。スペースに関して必要な参照は2つだけであり、CPUコストは、小さなメモリ割り当てと、呼び出し時の余分な間接参照に制限されます。ただし、このコストは、バインドされたメソッド機能を実際に使用するものだけでなく、すべてのメソッド呼び出しに適用されることに注意してください。動的言語では、何か別のことをするためにモンキーパッチが適用されているかどうかわからないため、記述子a.f()
を呼び出してその戻り値を使用する必要があります。
PyPyでは、物事はもっと興味深いものです。これは正確性を損なうことのない実装であるため、上記のモデルはセマンティクスについての推論には依然として正しいものです。ただし、実際には高速です。JITコンパイラは、ほとんどの場合、上記の混乱全体をインライン化して排除するという事実とは別に、バイトコードレベルで問題に取り組みます。2つの新しいバイトコード命令があります。これらはセマンティクスを保持しますが、の場合はバインドされたメソッドオブジェクトの割り当てを省略しますa.f()
。ルックアッププロセスを簡素化できるメソッドキャッシュもありますが、追加の簿記が必要です(ただし、その簿記の一部はJIT用にすでに実行されています)。