まずはこれを見てほしい。
>>> class X:
... def y(self):
... return 1
...
>>> ins = X()
>>> ins.y()
1
>>> X.y()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: y() missing 1 required positional argument: 'self'
>>> X.y(X())
1
メソッドはインスタンスにしないと実行できないとどっかのTechAc○demyブログで言っていたが半分嘘で、クラスに属する関数でもあるため普通に実行できてしまう。ただ、引数の違いが存在する。
続きを見ていこう。
>>> ins.y
<bound method X.y of <__main__.X object at 0x7fd6203f0d90>>
>>> X.y
<function X.y at 0x7fd61ebcbc10>
インスタンスから呼び出すのとクラスから呼び出すので属性が変わっている。前者はメソッド、後者は関数だ。
この差が第一引数に自動的にselfを代入してくれるかどうかの違いのようだ。
つまり、Pythonの内部で、メソッドが呼び出されると自動的にメソッドの元になっている関数を呼び出し(ins.y.__func__ == X.y
)、selfをつけて実行するのだ。
公式ドキュメントより:
呼び出された時、引数リストに self 引数が追加されます。
m.__func__
はそのメソッドを実装している関数です。
m(arg-1, arg-2, ..., arg-n)
の呼び出しは、m.__func__(m.__self__, arg-1, arg-2, ..., arg-n)
の呼び出しと完全に等価です。
https://docs.python.org/ja/3/library/stdtypes.html#methods
公式ドキュメントの
https://docs.python.org/ja/3/tutorial/classes.html#method-objects
をみるとより理解が深まることでしょう。
特にこの部分が詳しいです。
インスタンスの非データ属性が参照されたときは、そのインスタンスのクラスが検索されます。その名前が有効なクラス属性を表している関数オブジェクトなら、インスタンスオブジェクトと見つかった関数オブジェクト (へのポインタ) を抽象オブジェクト、すなわちメソッドオブジェクトにパックして作成します。メソッドオブジェクトが引数リストと共に呼び出されるとき、インスタンスオブジェクトと渡された引数リストから新しい引数リストを作成して、元の関数オブジェクトを新しい引数リストで呼び出します。