目次
結論。自分の理解まとめ。
インスタンスメソッド
インスタンスオブジェクト(object=class名())から実行すると、methodタイプのメソッドと認識され、自身のインスタンスが勝手に第一引数として渡される。
クラスオブジェクト(object=class名)から実行すると、functionタイプのメソッドと認識され、ただの関数として実行される。
つまり、実行のされ方によって、取り扱いが変わる。
クラスメソッド(@classmethod)
インスタンスオブジェクトから実行すると、methodタイプのメソッドと認識され、自身のクラスオブジェクト型が勝手に第一引数として渡される。
クラスオブジェクトから実行すると、methodタイプのメソッドと認識され、自身のクラスオブジェクト型が勝手に第一引数として渡される。
つまり、どのように実行しても強制的にmethodタイプのメソッドと認識され、自身のクラスオブジェクト型を第一引数として受け取る。
スタティックメソッド(@staticmethod)
インスタンスオブジェクトから実行すると、functionタイプのメソッドと認識され、ただの関数として実行される。
クラスオブジェクトから実行すると、functionタイプのメソッドと認識され、ただの関数として実行される。
つまり、どのように実行しても強制的にfunctionタイプのメソッドと認識され、ただの関数になる。
メモ書き
スタティックメソッド使う・・・?普通にクラス外に関数として定義するのと変わらない気がする。メソッド内でインスタンスを使わず、クラスに関わる処理しか行わない場合に、普通の関数でも良いがグルーピングする意味として使う感じか?
インスタンスメソッドについて。インスタンス化の有無でタイプが変わり、実行の仕方も変わる。
class my_class:
def instance_method(self):
print("instance_method")
def function():
print("function_method")
# インスタンス生成した場合
obj = my_class()
# 出力:<class '__main__.my_class'>
print (type(obj))
# 出力:<class 'method'>
print(type(obj.instance_method))
# 出力:<class 'method'>
print(type(obj.function))
# インスタンス生成しない場合
# 出力:<class 'type'>
print (type(my_class))
# 出力:<class 'function'>
print (type(my_class.instance_method))
# 出力:<class 'function'>
print (type(my_class.function))
クラスをインスタンス化し、そのオブジェクトから参照した場合、instance_method()、function()の2つのメソッドはmethodタイプになっていました。
インスタンス化せず、直接クラスから参照した場合は、functionタイプになっていました。
このように、インスタンス化の有無によって、メソッドのタイプが異なります。
これを踏まえて、メソッドの呼び出し実験をします。
メソッドのタイプによる違いとselfの有無の確認
前述のコードに対して、追記します。
# 出力:instance_method
obj.instance_method()
# 出力:TypeError: my_class.function() takes 0 positional arguments but 1 was given
obj.function()
instance_methodは無事に実行できましたが、functionの実行にはエラーが発生しています。
function()は引数0個しか取得できないが、1個渡されているぞと指摘されています。
そうは言われても、function()に引数なんて渡していない。何故勝手に1つ渡されているのか?というか渡されている引数は何者なのか?これも実験してみます。
# instance_methodに、引数のタイプを表示する処理を追加して、引数のタイプを見てみます。
def instance_method(self):
print("instance_method")
#これを追加
# 出力:<class '__main__.my_class'>
print(type(self))
引数のタイプは__main__.my_classでした。これはインスタンス化したオブジェクトのタイプを出力したときと同じですね。つまり引数として渡されているのは自身のインスタンスのようです。
これは先ほど確認した通り、function()のタイプがmethodとして呼び出されているから、このような動作をしています。
methodタイプでの呼び出し、つまりインスタンスから実行しますが、その際には自身のインスタンスを引数に渡しています。しかし、明示的にではなく、省略されて内部的に渡されます。
なので見えていないだけで、function()には自クラスのインスタンスが渡されているので、そのオブジェクトを受け取るための引数の定義がfunction()に必要ということでした。
一応、インスタンス化せず、実行してみます。
# 出力:TypeError: my_class.instance_method() missing 1 required positional argument: 'self'
my_class.instance_method()
# 出力:function_method
my_class.function()
逆にinstance_methodはエラーが発生して、functionの実行はできました。
instance_methodでは、必要な引数”self”が1つ無いといわれています。
ただの関数として実行されているので、自身のインスタンスが自動で渡されることは無いということですね。
実験の結果、インスタンス化有無による差が確認できました。
クラスメソッド(@classmethod)は何が違う?
インスタンスメソッドの1行前に「@classmethod」を書くとクラスメソッドになります。
なりますと言うのは簡単ですが、何が変わるのか分かりません。という訳で実験してみます。
class my_class:
def instance_method(self):
print("instance_method")
@classmethod
def class_method(self):
print("class_method")
# インスタンス生成した場合
obj = my_class()
# 出力:<class '__main__.my_class'>
print (type(obj))
# 出力:<class 'method'>
print(type(obj.instance_method))
# 出力:<class 'method'>
print(type(obj.class_method))
# 出力:instance_method
obj.instance_method()
# 出力:class_method
obj.class_method()
# インスタンス生成しない場合
# 出力:<class 'type'>
print (type(my_class))
# 出力:<class 'function'>
print (type(my_class.instance_method))
# 出力:<class 'method'>
print (type(my_class.class_method))
# 出力:instance_method
my_class.instance_method("")
# 出力:class_method
my_class.class_method()
上記の結果から、インスタンス化してからの実行はインスタンスメソッドもクラスメソッドも同じ結果に見えます。
注目すべきは、インスタンス化しない場合のクラスメソッドです。タイプがmethodになっています。そして、引数を渡していると、1つで良いのに2つも渡しているとエラーになります。(なので上記では引数をわたしていません。)
つまり、クラスメソッドにすると、関数として実行しても、インスタンスメソッドと同様になるということでしょうか?
もしも、そうだとすると、インスタンス化を勝手にされているということに。今回の例だと問題ないですが、もしもコンストラクタ等で初期化処理があり、インスタンス生成時には引数を渡す必要がある場合は、自動でインスタンス化できるとは思えません。
ということは逆説的に、インスタンスメソッドと同様に、引数が隠れて渡されていますが、自身のインスタンスでは無い何かが渡されていると思われます。
確認の実験をしてみます。
# class_methodに、引数のタイプを表示する処理を追加して、引数のタイプを見てみます。
@classmethod
def class_method(self):
print("class_method")
#これを追加
# 出力:<class 'type'>
print(type(self))
上記の結果から、インスタンス生成していないときのクラスオブジェクトのタイプと同様であるtypeとなりました。
自身のインスタンスが渡されている訳ではなく、自身のクラスオブジェクトの型が渡されています。これはインスタンス化の有無に関わらず、どちらの場合も同様にtypeでした。
つまり、クラスメソッドにすると、自身のクラスオブジェクト型を引数に渡しているということでした。
ちなみに、クラスオブジェクト型が渡されるので、引数名はselfではなく、clsと記載されるのが慣例です。慣例なので、上記のように名前はselfでも何でも良いですが。
まとめると、クラスメソッドにすると、必ずmethodタイプとして実行されるようになり、自身のインスタンスでは無くクラスオブジェクト型が渡されるということです。
スタティックメソッド(@staticmethod)は何が違う?
インスタンスメソッドの1行前に「@staticmethod」を書くとスタティックメソッドになります。
なりますと言うのは容易いですが、これまた何が変わるのか分かりません。という訳で引き続き実験してみます。
class my_class:
def instance_method(self):
print("instance_method")
@staticmethod
def static_method(self):
print("static_method")
# インスタンス生成した場合
obj = my_class()
# 出力:<class '__main__.my_class'>
print (type(obj))
# 出力:<class 'method'>
print(type(obj.instance_method))
# 出力:<class 'function'>
print(type(obj.static_method))
# 出力:instance_method
obj.instance_method()
# 出力:TypeError: my_class.static_method() missing 1 required positional argument: 'self'
#obj.static_method()
# 出力:static_method
obj.static_method("")
# インスタンス生成しない場合
# 出力:<class 'type'>
print (type(my_class))
# 出力:<class 'function'>
print (type(my_class.instance_method))
# 出力:<class 'function'>
print (type(my_class.static_method))
# 出力:instance_method
my_class.instance_method("")
# 出力:static_method
my_class.static_method("")
上記の結果から、インスタンス生成の有無にかかわらず、static_methodはfunctionタイプになっています。
functionタイプということで、ただの関数なので、引数も勝手に渡されることはありません。
なので、インスタンス生成してstatic_methodを実行したとしても、selfに自身のインスタンスが渡されることは無く、static_methodに引数をこちらで設定して渡さない場合はエラーになりました。(そのため、上記では適当に空を渡しています。そもそもselfの引数定義が必要ありません)
つまり、スタティックメソッドになると、強制的にfunctionタイプになり、ただの関数として扱われるようになるということです。