Python の関数とメソッドの違いって何?

疑問: 関数とメソッドって違うの?

答え: 関数とメソッドは違います。

メソッドは呼び出されると
 自分を呼び出してきたインスタンスオブジェクト
 クラスオブジェクトの属性に代入された関数の第一引数
代入して結果を返します。

以下の 1), 2) は "厳密に等価なもの" です。

>>> # 1) メソッドは呼び出されると
>>> girl_friend.change_name('岩倉玲音') 
>>>
>>> # 2) 自分を呼び出してきたインスタンスオブジェクトを
>>> #    クラスオブジェクトの属性に代入された関数の第一引数に
>>> #    代入して結果を返します。
>>> GirlFriend.change_name(girl_friend, '岩倉玲音')
>>> 

x.f() という呼び出しは、 MyClass.f(x) と厳密に等価なものです。
9.3.4. メソッドオブジェクト

インスタンスオブジェクトとクラスオブジェクトって何?

実は、この記事は以下の記事からの続きになります。インスタンスオブジェクトとクラスオブジェクトって何かをざっくりと押さえておいてください。


◯ ここで題材に取り上げるサンプルコード

例にもれず、また次のような GirlFriend クラスを考えてみましょう。

class GirlFriend(object):
    min_intimacy = 0
    max_intimacy = 100
    
    def __init__(self, name=''):
        self.name = name
        self.intimacy = 0
    
    def change_name(self, name):
        self.name = name

実際に触ってメソッドと関数の動作を比較する

クラスオブジェクトの属性に代入されるのは "関数" です。"メソッド" ではありません。クラス定義文の中で定義された関数は、クラスオブジェクトの属性に代入されます。実際にクラスオブジェクトの属性に代入された関数を参照して見ましょう。

>>> # 属性を参照するとメソッドではなく
>>> # 関数が代入されていることがわかります。
>>> GirlFriend.change_name
<class 'function'>
>>>

◯ 関数

このクラスオブジェクトの属性に代入された "関数" を使って、"関数" の動作を確認してみます。

>>> #  Step 1: 関数の確認
>>> GirlFriend.change_name
<function GirlFriend.change_name ...
>>> 
>>>
>>> # Step 2: 第一引数を省略して呼び出す。
>>> # 関数 change_name は2つの引数 self, name が必要です
>>> # 引数を1つだけ与えるとエラーが返されます。
>>> GirlFriend.change_name('岩倉玲音')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: change_name() missing 1 required
positional argument: 'name'
>>>
>>> # Step 3: 第一引数を省略しないで呼び出す。
>>> girl_friend = GirlFriend() 
>>> GirlFriend.change_name(girl_friend, '岩倉玲音')
>>> girl_friend.name
'岩倉玲音'
>>>

 

◯ メソッド

クラスオブジェクトからインスタンスオブジェクトが生成されると、"関数" は "メソッド" として代入されます。"メソッド" は第一引数に、呼び出し元のオブジェクトが与えられます。

>>> # Step 1: メソッドの確認
>>> girl_friend = GirlFriend()
>>> girl_friend.change_name
<bound method GirlFriend.change_name ...
>>> 
>>> # Step 2: 第一引数を省略して呼び出す。
>>> girl_friend.change_name('岩倉玲音')
>>> girl_friend.name
'岩倉玲音'
>>>

クラスオブジェクトの属性(関数)を変更すると、
そのクラスオブジェクトから生成された
全てのインスタンスオブジェクトの属性(メソッド)も
変更される。

実際の動作を見て見ましょう。メソッドを変更したい時は、クラスオブジェクトの属性に関数を代入します。

>>> girl_friend1 = GirlFriend()
>>> girl_friend2 = GirlFriend()
>>> 
>>> girl_friend1.change_name('岩倉玲音')
>>> girl_friend2.change_name('バラライカ')
>>> 
>>> girl_friend1.name
'岩倉玲音'
>>> girl_friend2.name
'バラライカ'
>>>
>>> # new_change_name 関数を定義しました。
>>> def new_change_name(self):
...   self.name = 'サーバルちゃん'
... 
>>>
>>>
>>> 
>>> # Step 1. クラスオブジェクトの属性に代入する
>>> # 正 GirlFriend.change_name = new_change_name
>>> # 誤 girl_friend1.change_name = new_change_name
>>> GirlFriend.change_name = new_change_name
>>> 
>>>
>>> # Step 2. インスタンスオブジェクトのメソッドは全て変更される。
>>> girl_friend1.change_name()
>>> girl_friend1.name
'サーバルちゃん'
>>> 
>>> girl_friend2.change_name()
>>> girl_friend2.name
'サーバルちゃん'
>>>

疑問: なぜクラスオブジェクトの属性に関数を代入したら配下のインスタンスオブジェクトのメソッドも全て変更されるの?

答え: メソッドを呼び出されるとその度に、クラスオブジェクトの関数にインスタンスオブジェクトを代入して実行しているだけから。

>>> # これが呼び出されると
>>> girl_friend.change_name('岩倉玲音') 
>>>
>>> # 自動的にこれが呼びされている。
>>> GirlFriend.change_name(girl_friend, '岩倉玲音') 
>>>

関数オブジェクトからインスタンスメソッドオブジェクトへの変換は、
インスタンスから属性が取り出されるたびに行われます。
呼び出し可能型 (callable type) > インスタンスメソッド

◯ もう少し詳しい動作

>>> # これが呼び出されると
>>> girl_friend.change_name('岩倉玲音')
>>>
>>> # 自動的にこれが呼びされている。
>>> girl_friend.change_name.__func__(
...     girl_friend.change_name.__self__, '岩倉玲音')
>>>

インスタンスメソッドオブジェクトが呼び出される際、根底にある関数 (__func__) が呼び出されます。このとき、クラスインスタンス (__self__) が引数リストの先頭に挿入されます。例えば、 C を関数 f() の定義を含むクラス、 x を C のインスタンスとすると、 x.f(1) の呼び出しは C.f(x, 1) の呼び出しと同じです。
呼び出し可能型 (callable type) > インスタンスメソッド


__func__ は GirlFriend.change_name を
__self__ は girl_friend オブジェクトをそれぞれ参照している。

>>> girl_friend.change_name.__func__ is GirlFriend.change_name
True
>>>
>>> girl_friend.change_name.__self__ is girl_friend
True

3.2. 標準型の階層 > 呼び出し可能型 (callable type) > インスタンスメソッド

疑問: インスタンスメソッドオブジェクトって何?

答え: メソッドです。

メソッドもインスタンスオブジェクトです。このことは、メソッドが変数に代入できることからもわかります。メソッドもインスタンスオブジェクトであることを強調するためにこのように表現しているものと思われます。

>>> # メソッドも変数に代入できる。
>>> # -> メソッドもインスタンスオブジェクト
>>> # -> インスタンスメソッドオブジェクト
>>> change_name_method = girl_friend.change_name
>>>

◯ 最適化

属性から取り出されるたびに、関数ではなくメソッドを参照するように変換が行われるので、最初からメソッドを変数に代入しておくと少しだけ処理を速くできます。

なお、関数オブジェクトからインスタンスメソッドオブジェクトへの変換は、インスタンスから属性が取り出されるたびに行われます。場合によっては、属性をローカル変数に代入しておき、そのローカル変数を呼び出すようにするのが効果的な最適化になります。
呼び出し可能型 (callable type) > インスタンスメソッド


◯ ちょっとした動作確認

マニュアルでは「変換」という言葉が使われているのでインスタンスメソッドオブジェクトが「生成」されているのかなと思いました。

しかし、たんに「切替」ているだけのようです。その都度、インスタンスメソッドオブジェクトを「生成」するようなことはしていなさそうです。

>>> girl_friend = GirlFriend('岩倉玲音')
>>> id(girl_friend.change_name)
4533991496
>>>
>>> # identity が同じ 
>>> # -> 同じインスタンスメソッドオブジェクトを使いまわしてる。
>>> id(girl_friend.change_name)
4533991496
>>> 


関数を代入するとインスタンスメソッドオブジェクトが新しく生成される。

>>> girl_friend = GirlFriend('岩倉玲音')
>>> id(girl_friend.change_name)
4533991496
>>> 
>>> # identity が違う
>>> # -> インスタンスメソッドオブジェクトを新規に生成している。
>>> def new_change_name(self):
...   self.name = 'サーバルちゃん'
... 
>>> GirlFriend.change_name = new_change_name
>>> id(girl_friend.change_name)
4469422216
>>> id(girl_friend.change_name)
4469422216

疑問: なぜ self を書くの?

答え: 関数として再利用できなくなるから

「暗黙的に self に関数を呼び出してきたインスタンスオブジェクトを代入するような設計」も考えることができる。

# self を書かなくても
# 使えるようにすればいいじゃん!
class GirlFriend(self):
    max_intimacy = 100
    min_intimacy = 0

    def __init__(name=''):
        self.name = name
        self.intimacy = 0

    def change_name(name):
        self.name = name


しかし、このように設計すると関数として使用したい時に、いくつかあたりがある時がある。その例を 3 つ見てみましょう。

例 1. モジュールの直下や関数の直下から呼び出す時

汎用的な関数を定義した時、この手の関数はモジュール、あるいは関数から呼び出したい時がある。もし「self に関数を呼び出してきたインスタンスオブジェクトを暗黙的に代入するような設計」だと、 self にモジュールや関数が代入されることになり、再利用できなくなる。

# self に sys.modules[__name__] が代入されてしまう
change_name('サーバルちゃん'):
def func():
    # self に func が代入されてしまう
    change_name('サーバルちゃん'):

例 2. 親クラスの関数を呼び出す時

親クラスのメソッドを呼び出したい時がある。 もし「self に関数を呼び出してきたインスタンスオブジェクトを暗黙的に代入するような設計」だと、self にクラスモジュールが代入されてしまい、再利用できなくなる。

class A:
    message = "Hello, world!"
    def f():
        print(self.message)

class B(A):
    message = "Nihao, shijie!"  # 你好,世界!
    def f():
        # 親クラスの関数 f を再利用して
        # "Nihao, shijie!" を表示したいけど

        # self にクラスオブジェクトの A が
        # 自動的に代入されて
        # "Hello, world!" が表示されてしまう。
        A.f()

第二に、特定のクラスからメソッドを明示的に参照または呼び出ししたい時に、特別な構文が必要なくなります。C++ では、派生クラスでオーバーライドされた基底クラスからメソッドを使うには、 :: 演算子を使わなければなりません。 Python では、 baseclass.methodname(self, ) と書けます。これは特に、 __init__() メソッドに便利ですし、派生クラスのメソッドが、基底クラスにある同じ名前のメソッドを拡張するために、基底クラスのメソッドをどうにかして呼び出したい時にも便利です。
なぜメソッドの定義や呼び出しにおいて ‘self’ を明示しなければならないのですか? | デザインと歴史 FAQ

例 3. classmethod, staticmethod デコレータ

classmethod デコレータは、第一引数にメソッドを呼び出してきたインスタンスオブジェクトではなくクラスオブジェクトを代入します。

staticmethod デコレータは、第一引数にメソッドを呼び出してきたインスタンスオブジェクトを代入することを省略します。

このような動作は、通常の Python のデコレータの動作で実装することができます。
Python のデコレータ

しかし、「self に関数を呼び出してきたインスタンスオブジェクトあるいはモジュールを暗黙的に代入するような設計」をされると、そのような動作をデコレータで実装できなくなります。

代替手段として classmethod デコレータ については、 メソッド内で
cls = self.__class__を呼び出せばいいだけじゃんという話もあるかもしれません。しかし、デコレータとしてクラスメソッドであることを明示できるのは、可読性において利点だと思います。

代替手段として staticmethod デコレータについては、メソッド内で暗黙的に代入された self を使わなければいいじゃんという話かもしれません。しかし、デコレータとしてスタティックメソッドであることを明示できるのは、可読性において利点だと思います。また self を触らせないようにするのは、想定外の実装されるのを避けるというメンテナンス面において利点であると思います。

繰り返しになりますが「self に関数を呼び出してきたインスタンスオブジェクトあるいはモジュールを暗黙的に代入するような設計」をされると、classmethod, staticmetod デコレータを実装できなくなります。

そうなってくると処理系のレベルで、classmethod, staticmetod デコレータのための特別な実装が必要になります。Guido はそのような実装をすることを却下しています。

"@classmethod"や"@staticmethod"を特別な記法で代替するハックを私は却下する。また,メソッドの定義だけを検査して,自動的にクラスメソッドかインスタンスメソッドかを決めるような手法が良いアイデアだとも思わない(実際だれかがBruceのブログにコメントする形で提案していたが)。そのような仕様を採用すると,メソッドの前にはdefという宣言があるだけなのに,メソッドは複数の異なった呼ばれ方をすることになる。どのように呼ばれるかを判断しづらくなる。
和訳 なぜ Python のメソッド引数に明示的に self と書くのか


上記の記事は Pythonの生みの親 Guido によって書かれたブログを和訳したものです。Bruce という人が「第一引数にわざわざ self を与えなくたって、暗黙的に呼び出し元のインスタンスオブジェクトを self に代入すればいいじゃん」と提案したのに対して、Guido が、それがなぜダメなのかの理由を述べています。

Special cases aren't special enough to break the rules.
特別なケースは、一貫性を破るほど特別ではない。
PEP 20 -- The Zen of Python

◯ まとめ

一見して self を省略して書けた方が、一貫性のある綺麗な書き方ができるようにも見えます。しかしながら、Python の関数はインスタンスオブジェクトとして扱うことができ、変数または他のインスタンスオブジェクトの属性に代入できるため、再利用性が高いです。そのため「self に関数を呼び出してきたインスタンスオブジェクトあるいはモジュールを暗黙的に代入する」ような制約のある設計をしてしまうと、関数の再利用性に制限がかかってしまいます。

Explicit is better than implicit.
明示的であることは暗黙的であるより良い。
PEP 20 -- The Zen of Python

終わりに

自分も self を頭に書くと一貫性がひどく損なわれる気がしたので Bruce さんと同じこと考えてました。しかも、これさえなければ Python は素晴らしいのに、とさえ思っていました。なので、Bruce さんがコテンパンに貶されてるのを見て凹みました。

では、なぜ self を第一引数に書くと不自然だと感じるのでしょうか?それは「メソッドを定義していると思っているから」です。厳密に言えばクラス定義の中で書いてるのは、関数定義です。メソッドではなく関数を定義しています。

実際に Python 言語リファレンスを覗いてみても funcdef 関数定義文 と言うのはあったのですが methoddef のようなメソッド定義文に該当しそうなものはありませんでした。

compound_stmt ::=  if_stmt
                   | while_stmt
                   | for_stmt
                   | try_stmt
                   | with_stmt
                   | funcdef <- 関数定義
                   | classdef
                   | async_with_stmt
                   | async_for_stmt
                   | async_funcdef

8. 複合文

普段は、関数定義、それともメソッド定義だと言っても、きっと、どちらでもいいと思います。何故なら、クラスオブジェクトから見れば関数の定義だし、インスタンスオブジェクトから見ればメソッドの定義になるので。

コードを書く人が、関数を定義する。そして、関数が代入されたクラスオブジェクトの属性を参照されると、誰かが、関数の第一引数に関数を呼び出してきたインスタンスオブジェクトを代入して関数を実行する。

では、一体誰が、このような動作を実装しているのでしょうか?ディスクリプタという言葉を理解することによって、その動作が理解できます。ぜひ、挑戦してみてください。

Python のオブジェクト指向機能は、関数に基づく環境の上に構築されています。非データデスクリプタを使って、この 2 つは(関数とメソッドは)シームレスに組み合わされています。
デスクリプタ HowTo ガイド