Last Updated: 2/6/2024, 5:44:57 AM

# 関数とメソッドの違いってなに?

第一引数に
自分を呼び出したオブジェクトを
代入するか、しないか

# はじめに

 次のコードを対話モードにコピペして実行してみてください。  対話モードというのは、あのトンガリマークが3つ連なった >>> 記号が表示される画面ことです。

class GirlFriend:
    def change_name(self, name):
        self.name = name

girl_friend = GirlFriend()



# 1) メソッドでの呼び出し方
girl_friend.change_name('岩倉玲音') 
print(girl_friend.name)  # 岩倉玲音


# 2) 関数での呼び出し方
GirlFriend.change_name(girl_friend, 'サーバルちゃん')
print(girl_friend.name)  # サーバルちゃん

まったく同じ動作をしています。

>>> # 1) メソッドでの呼び出し方
... girl_friend.change_name('岩倉玲音') 
>>> print(girl_friend.name)  # 岩倉玲音
岩倉玲音
>>> 
>>> # 2) 関数での呼び出し方
... GirlFriend.change_name(girl_friend, 'サーバルちゃん')
>>> print(girl_friend.name)  # サーバルちゃん
サーバルちゃん
>>> 

トンガリマーク >>> が表示される時の正式な名称は、 対話モード interactive mode と言います。 覚える必要はないので、ご安心ください。

インタプリタが命令を端末 (tty) やコマンドプロンプトから読み取っている場合、 インタプリタは 対話モード (interactive mode) で動作しているといいます。
2.1.2. 対話モード- Python チュートリアル (opens new window)

これから先「コピペしてください」とお願いをさせていただきますが、 そのときは、このトンガリマーク >>> にコピペしてください。 ファイルを作っていただかなくてもいいように調整いたしました。

Hello, world!

# 1. メソッドの仕組み

メソッド 1, 関数 2 を実行すると同じ結果が得られます。

# 1) メソッドでの呼び出し方
girl_friend.change_name('岩倉玲音') 


# 2) 関数での呼び出し方
GirlFriend.change_name(girl_friend, '岩倉玲音') 

x.f() という呼び出しは、 MyClass.f(x) と厳密に等価なものです。
9.3.4. メソッドオブジェクト - Python チュートリアル (opens new window)

実は、メソッドが呼び出されると、 単純に関数が呼び出されてるだけです。

# 1) メソッドが呼び出されると
girl_friend.change_name('岩倉玲音') 


# 2) 関数に変換されて実行される。
GirlFriend.change_name(girl_friend, '岩倉玲音') 

関数オブジェクトからインスタンスメソッドオブジェクトへの変換は、 インスタンスから属性が取り出されるたびに行われます。
3.2. 標準階層 - Python 言語リファレンス (opens new window)

ポイント

メソッドは関数に変換されています。

# 2. 疑問: self ってなに?

 答え: 関数のいちばん最初の引数です。 

たくさん self を書かないといけないので、 self は、特別な何かなのかなと混乱してしまいます。

上でコピペして実行した通りメソッドは、単純に関数を呼び出しているだけです。 self は、たんなる関数の第一引数、いちばん最初の引数でしかありません。

# ◯ 実験

実際に self は関数の引数なので、 別に self と書かなくてもメソッドは問題なく動きます。

 次のコードをコピペして実行してみてください。 

class GirlFriend:
    def change_name(girl_friend, name):  # self じゃなくても動く。
        girl_friend.name = name

person = GirlFriend()
person.change_name('岩倉玲音')
person.name  # 岩倉玲音

問題なく動作します。

>>> person = GirlFriend()
>>> person.change_name('岩倉玲音')
>>> person.name  # 岩倉玲音
'岩倉玲音'
>>> 

理解するために意識しておいて欲しいことは、  クラスの中で書いてるのは関数です  ということです。

class GirlFriend:
    def change_name(girl_friend, name):  # <- 関数を書いてます。
        girl_friend.name = name

# ◯ PEP 8

ちなみに self と書かないといけないのは PEP 8 という Python のコーディング規約で決められています。 self は機能ではなく、そうやって書いてくださいね、という決まりごとでしかありません。

インスタンスメソッドの第一引数には、self を使ってください。
Always use self for the first argument to instance methods.
PEP 8 - Style Guide for Python Code (opens new window)

PEP 8 がなにかについては、以下の記事で見てきました。 self と書かなくても動くのに、 なんで、みんなが PEP 8 に従って self を書いているのかについて、考えていきます。 読まなくても大丈夫です。

ポイント

self は関数の第一引数です。

# 3. 触って比較してみる。

とは言え、言葉で説明されても分からないので、 実際に触って比較して理解を試みたいと思います。 「関数」と「メソッド」を実際にコピペして操作して、動作を確認してみたいと思います。

# ◯ 用語

事前準備としてについて「インスタンスオブジェクト」、「メソッド」、 「クラスオブジェクト」、「関数」という4つの言葉について、ご説明させてください。

# 1. インスタンスオブジェクトとメソッド

クラスからインスタンス化すると「インスタンスオブジェクト」が生成されます。 インスタンスオブジェクトの属性には、「メソッド」が代入されます。

 次のコードを対話モードにコピペして実行してみてください。 

class GirlFriend:
    def change_name(self, name):
        self.name = name

girl_friend = GirlFriend()              
girl_friend              # <- これはインスタンスオブジェクト
girl_friend.change_name  # <- これはメソッド

>>> girl_friend = GirlFriend()              
>>> girl_friend              # <- これはインスタンスオブジェクト
<__main__.GirlFriend object at 0x10d706198>
>>> girl_friend.change_name  # <- これはメソッド
<bound method GirlFriend.change_name of <__main__.GirlFriend object at 0x10d706198>>
       ^^^^^^ 「英語」でメソッドと書かれています。
>>> 

# 2. クラスオブジェクトと関数

クラス定義文を抜けると「クラスオブジェクト」が生成されます。 そしてクラスオブジェクトの属性には、「関数」が代入されます。

 次のコードを対話モードにコピペして実行してみてください。 

class GirlFriend:
    def change_name(self, name):
        self.name = name

GirlFriend              # <- これはクラスオブジェクト
GirlFriend.change_name  # <- これは関数
>>> GirlFriend              # <- これはクラスオブジェクト
<class '__main__.GirlFriend'>
>>> GirlFriend.change_name  # <- これは関数
<function GirlFriend.change_name at 0x10e667598>
 ^^^^^^^^ 英語で「関数」と書かれています
>>> 

ここで意識していただきたいことは。  クラスオブジェクトの属性に代入されるのは「メソッド」ではなく「関数」だ  ということです。 クラス定義文の中で定義された関数は、クラスオブジェクトの属性に代入されます。

# ◯ 実験

「インスタンスオブジェクト」と「クラスオブジェクト」について、 それぞれ次の操作を行い、結果を確認してみたいと思います。

Step 1: 属性を参照してみる。
Step 2: 第一引数を省略して呼び出してみる。
Step 3: 第一引数を省略しないで呼び出してみる。

# 実験 1. インスタンスオブジェクトとメソッド

それでは、実験をはじめていきます。  次のコードを対話モードにコピペして実行してみてください。  ちなみに Step 3 の操作は元からエラーが投げられるのが正常な動作です。ご安心ください。

class GirlFriend:
    def change_name(self, name):
        self.name = name

girl_friend = GirlFriend()



# Step 1: 属性を参照してみる。
girl_friend.change_name


# Step 2: 第一引数を省略して呼び出してみる。
girl_friend.change_name('サーバルちゃん')
girl_friend.name


# Step 3: 第一引数を省略しないで呼び出してみる。
girl_friend.change_name(girl_friend, 'サーバルちゃん')

コピペできましたでしょうか? 実行結果を各 Step ごとに確認していきたいと思います。

# (Step 1)

method と書かれた文字が見えます。 属性を参照すると関数ではなく メソッドが代入されていることがわかります。

>>> # Step 1: 属性を参照してみる。
>>> girl_friend.change_name
<bound method GirlFriend.change_name of <__main__.GirlFriend object at 0x10b5af240>>
>>> 

# (Step 2)

>>> # Step 2: 第一引数を省略して呼び出す。
>>> girl_friend.change_name('サーバルちゃん')
>>> girl_friend.name
'サーバルちゃん'
>>>

# (Step 3)

メソッドは、第一引数が自動的に代入されます。 2つのメソッドを与えるとエラーになります。

>>> # Step 3: 第一引数を省略しないで呼び出す。
>>> girl_friend.change_name(girl_friend, 'サーバルちゃん')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: change_name() takes 2 positional arguments but 3 were given
>>>

エラー文を和訳すると次のようなものです。

TypeError: change_name は2つの位置引数をとりますが、3つも与えられました。
TypeError: change_name() takes 2 positional arguments but 3 were given

# 実験 2. クラスオブジェクトと関数

 次のコードを対話モードにコピペして実行してみてください。  ちなみに Step 2 の操作は元からエラーが投げられるのが正常な動作なので、ご安心ください。

class GirlFriend:
    def change_name(self, name):
        self.name = name

girl_friend = GirlFriend()



# Step 1: 属性を参照してみる。
GirlFriend.change_name


# Step 2: 第一引数を省略して呼び出してみる。
GirlFriend.change_name('岩倉玲音')
girl_friend.name


# Step 3: 第一引数を省略しないで呼び出してみる。
GirlFriend.change_name(girl_friend, '岩倉玲音')
girl_friend.name

# (Step 1)

function という文字が見えます。 属性を参照するとメソッドではなく 関数が代入されていることがわかります。

>>> # Step 1: 属性を参照してみる。
>>> GirlFriend.change_name
<function GirlFriend.change_name at 0x106ee47b8>
>>> 

# (Step 2)

関数 change_name は2つの引数 self, name が必要です 引数を1つだけ与えても TypeError が投げ返されてしまいます。

>>> # Step 2: 第一引数を省略して呼び出してみる。
>>> GirlFriend.change_name('岩倉玲音')
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
TypeError: change_name() missing 1 required positional argument: 'name'
>>>

エラー文を和訳すると次のようなものです。

TypeError: change_name 関数が呼び出されましたが、1つの必須の位置引数 name が見つかりません。
TypeError: change_name() missing 1 required positional argument: 'name'

GirlFriend.change_name(self, name) 関数は、 2つの引数が必要なのに1つの引数 self には '岩倉玲音' が与えられたけど、 まだ name が足りないよ、と言われています。

# (Step 3)

問題なく実行できます。

>>> # Step 3: 第一引数を省略しないで呼び出してみる。
... GirlFriend.change_name(girl_friend, '岩倉玲音')
>>> girl_friend.name
'岩倉玲音'
>>> 
>>> 

# ◯ まとめ

「インスタンスオブジェクト」の属性には「メソッド」が代入されます。 それに対して「クラスオブジェクト」の属性には「関数」が代入されます。

「メソッド」は呼び出されると、自動的に第一引数を「関数」に代入してくれます。 「メソッド」は単純に「関数」に変換しているだけです。

x.f() という呼び出しは、 MyClass.f(x) と厳密に等価なものです。
9.3.4. メソッドオブジェクト - Python チュートリアル (opens new window)

関数オブジェクトからインスタンスメソッドオブジェクトへの変換は、 インスタンスから属性が取り出されるたびに行われます。
3.2. 標準階層 - Python 言語リファレンス (opens new window)

ポイント

インスタンスオブジェクトとクラスオブジェクトを区別する。

# 4. 疑問: なんで自動的に代入するの?

なんでメソッドは、第一引数 self にインスタンスを自動的に代入してくれるの?
答え: 書きやすいし、読みやすくなるから

もし、第一引数 self にインスタンスを自動的に代入してくれなかった場合、 書き方2 のようになります。書き方1 に比べると面倒くさそうですね。

#
# 対話モード >>> に
# コピペで実行できます。
#
class GirlFriend:
    def change_name(self, name):
        self.name = name

girl_friend = GirlFriend()

# 書き方1. メソッド
girl_friend.change_name('サーバルちゃん')
girl_friend.name

# 書き方2. 関数
GirlFriend.change_name(girl_friend, '岩倉玲音')
girl_friend.name

>>> # 書き方1. メソッド
>>> girl_friend.change_name('サーバルちゃん')
>>> girl_friend.name
'サーバルちゃん'
>>> 
>>> # 書き方2. 関数
>>> GirlFriend.change_name(girl_friend, '岩倉玲音')
>>> girl_friend.name
'岩倉玲音'
>>> 

# ◯ その他の例

Python には、他にも様々なコードを短くする方法が用意されています。 例えばリストを合成したクラス Container の例を示します。  次のコードを対話モードにコピペして実行してみてください。 

#
# 対話モード >>> に
# コピペで実行できます。
#
class Container:
    def __init__(self, *args):
        self._values = args
    
    def __getitem__(self, index):
        return self._values[index]
    
    def __iter__(self):
        return iter(self._values)
    
    def __len__(self):
        return len(self._values)

container = Container(0, 1, 2)


# 1) 直接、添字表記で参照できる。
container[0]
container[1]
container[2]

# 2) 直接、for 文で回せる。
for element in container:
    element

# 3) 直接、len 関数でオブジェクトの長さを取得できる。
len(container)
>>> # 1) 直接、添字表記で参照できる。
... container[0]
0
>>> container[1]
1
>>> container[2]
2
>>> 
>>> # 2) 直接、for 文で回せる。
... for element in container:
...     element
... 
0
1
2
>>> # 3) 直接、len 関数でオブジェクトの長さを取得できる。
... len(container)
3
>>> 

他のオブジェクトに対する参照をもつオブジェクトもあります; これらは コンテナ (container) と呼ばれます。 コンテナオブジェクトの例として、 タプル、リスト、および辞書が挙げられます。
3.1. オブジェクト、値、および型 - Python 言語リファレンス (opens new window)

実際のところ、 上記の例で言えば tuple クラスを継承すれば一瞬で実装できます。 上記のコードは意味のないサンプルコードなのでしょうか?

class Container(tuple):
    def __new__(cls, *args):
        return super().__new__(cls, args)

container = Container(0, 1, 2)


# 1) 直接、添字表記で参照できる。
container[0]
container[1]
container[2]

# 2) 直接、for 文で回せる。
for element in container:
    element

# 3) 直接、len 関数でオブジェクトの長さを取得できる。
len(container)

クラスを継承せずに属性に代入する方法を、カッコつけて 「合成」と言ったりします。 上の例で言えば _values という属性に合成していました。 親クラスと子クラスの名前空間を明確に分割したいときに使います。

# ◯ まとめ

self って、なんだ?という混乱を招いてはしまいますが。 デメリットよりも、メリットの方が大きいかなと思います。 こうやって便利な機能を実装すると便利になり可読性もあがったりします。 ただ反面、理解はしづらくなります。

PEP 8 では1行を 79 文字と制限されているので、 適切に、このような機能を使い、簡潔なコードを書くことが求められているのかなと、 感じたりもします。

すべての行を最大 79 文字に制限する。
Limit all lines to a maximum of 79 characters.
PEP 8 -- Style Guide for Python Code (opens new window)

理解しにくくなるけど便利にしようかな、 それとも理解しやすくしておいて面倒だけどガリガリ書き込んでもらうようにしようかなと、 決断を迫られる場面が出てくるのではないかなと思います。

ポイント

便利な機能は理解しにくい時がある。

# おわりに

メソッドは、単純にクラスで定義された関数を呼び出しているだけです。 self は、その呼び出された関数の第一引数でしかありません。 第一引数は self と書くように PEP 8 という文書で決められています。

ここまで以下のように見てきました。

# ◯ ここから先は

さらにメソッドに触れていきます。 関数からメソッドに変換される仕組みと、 なんで self って書かないといけないのかという文法の背景を見ていきます。

もし、Python を習いたてで、すでに、お腹いっぱいだったり、 まだ興味がわかなければ、無理しなくていいのかなと思います。 何故なら、ここから先のことを知らなくても、大抵のことはできるからです。

知っていれば、複雑なことができるようになりますが。 YAGNI あるいは KISS の原則という言葉がある通り、 特に理由がない限りは複雑なことはしない方が良いと感じるからです。

実際 Go 言語は複雑な機能を避け、単純なものだけになっています。 いろんなものを作ってみて興味が湧いたら、また来ていただけると嬉しいです。

# 1. クラスメソッドとスタティックメソッド

普通のメソッドとはちょっと変わった動作をするメソッドを定義することができます。 これを土台にして次と、その次の記事を見ていきます。

# 2. 誰が関数からメソッドに変換しているの?

この記事ではメソッドが呼び出されると 関数に変換されて実行されていることを見てきました。

では、このメソッドから関数への変換は、 一体、誰がどのようにして行なっているのでしょうか? それは「ディスクリプタ」です。

「ディスクリプタ」を使い、 「メソッド」、「クラスメソッド」、「スタティックメソッド」を実装して動作の見ていきます。

# 3. なんで self を書くの?

メソッドを定義する時に self と書くのは面倒ではないでしょうか? 自動的に self と書いてくれればもっと楽になるはずです。

class GirlFriend:
    # いちいち self を書くのは面倒
    def __init__(self, name):
        self.name = name
class GirlFriend:
    # self を書かないようにすればいいのに...
    def __init__(name):
        self.name = name

なぜ self と書くべきかについて Python の開発者 Guido van Rossum がメールで解説してくれました。 そのメールの中には、もし self を書かなかったら「クラスメソッド」や「スタティックメソッド」を定義できないよね。 という文章があります。

以下の記事では self をなぜ書くべきかについて書かれた Guido のメールを見ていきます。