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













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





とりあえず、次のコードをコピペして実行してみてください。

class GirlFriend(object):
    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)  # サーバルちゃん
サーバルちゃん
>>> 

◯ メソッドの動作

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

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

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

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



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

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

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

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




疑問: self ってなに?

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

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

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

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

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

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


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

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


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

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



とは言え、言葉で説明されても分からないので、 実際に触って比較して理解を試みたいと思います。

2. 実際に触って、メソッドを関数と比較してみる。


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

◯ 関数

クラスオブジェクトの属性に代入されるのは "関数" です。"メソッド" ではありません。クラス定義文の中で定義された関数は、クラスオブジェクトの属性に代入されます。

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

GirlFriend  # <- クラスオブジェクト
>>> GirlFriend  # <- クラスオブジェクト
<class '__main__.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 という文字が見えます。
>>> #         属性を参照するとメソッドではなく
>>> #         関数が代入されていることがわかります。
>>> GirlFriend.change_name
<function GirlFriend.change_name at 0x10b59f1e0>
>>>
>>> # 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: 第一引数を省略しないで呼び出してみる。
>>> GirlFriend.change_name(girl_friend, '岩倉玲音')
>>> girl_friend.name
'岩倉玲音'
>>>

 

◯ メソッド

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

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

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

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


>>> # Step 1: 属性を参照してみる。
>>> #         method と書かれた文字が見えます。
>>> #         属性を参照すると関数ではなく
>>> #         メソッドが代入されていることがわかります。
>>> girl_friend.change_name
<bound method GirlFriend.change_name of <__main__.GirlFriend object at 0x10b5af240>>
>>> 
>>> # Step 2: 第一引数を省略して呼び出す。
>>> girl_friend.change_name('サーバルちゃん')
>>> girl_friend.name
'サーバルちゃん'
>>>
>>> # 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
>>>

疑問: なんでメソッドは、第一引数 self にインスタンスを自動的に代入してくれるの?

答え: 読みやすくなるから

もし、第一引数 self にインスタンスを自動的に代入してくれなかった場合、こういう書き方になります。2回も girl_friend を書いていて冗長です。

# (偽物)メソッド呼び出し
girl_friend.change_name(girl_friend, 岩倉玲音')


そこで第一引数 self に自分を呼び出したインスタンスを、自動的に代入しておいてもらった方が読みやすいコードになります。

# (本物)メソッド呼び出し
girl_friend.change_name('岩倉玲音')


self って、なんだ?という混乱を招いてはしまいますが。デメリットよりも、メリットの方が大きいかなと思います。

こうやって便利な機能を実装することは、便利になる反面、理解しづらくなる感覚を、なんとなくおさえておいてください。

みなさんにも便利にしようかな、それとも面倒だけどガリガリ書き込んでもらうようにしようかなと、決断を迫られる場面が必ず出てくるはずです。

◯ まとめ

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














ここまでは、関数とメソッドの動作の違いを確認しました。

もし、Python を習いたてで、すでに、お腹いっぱいだったり、まだ興味がわかなければ、ここまでで十分だと思います。 何故なら、ここから先のことを知らなくても、十分にアプリは作れるからです。

確かに、知っていれば、もう少し複雑なことができるようになりますが、 いろんなアプリを作ってみて、メソッドの動作の中身がどうなってるのか気になったら、また来ていただけると嬉しいです。

頑張って書いては見ましたが、オブジェクトについて具体的なイメージがない時にここから先の文章を読んでも、 抽象的すぎて役に立たないどころか、苦痛になってしまうと思います。

いまやらないで、学ぶタイミングを見計らうことは、きっと効率的に学習する上で大切だと思っています。

f:id:domodomodomo:20140904214333j:plain:w300 f:id:domodomodomo:20140909103527j:plain:w300
中国甘粛省
















(後編)メソッドの設計背景と仕組み


ここから先は、メソッドの設計背景と仕組みについて説明します。 具体的には次の2つのことを説明します。

1つ目は、なぜ self を書いてまでメソッドを関数で定義するのか。 なぜなら self を書いてまでメソッドを関数で定義した方が、一貫性が高いから。

2つ目は、メソッドへの変換は誰が行なっているのか。 関数からメソッドへの変換は、ディスクリプタが行なっています。





1 章...疑問: メソッドを関数の違いって何?
2 章...メソッドを関数と比較してみる。
3 章...疑問: メソッドと関数は、どう使い分けるの?
4 章...メソッドを変更してみる。
5 章...疑問: なんで self を書くの?
6 章...疑問: なんで全てのメソッドが変更されたの?
7 章...メソッドの呼び出しを高速化してみる。
8 章...疑問: 関数からメソッドへの変換って何をしているの?
9 章...疑問: 誰が関数からメソッドに変換しているの?

3. 疑問: どんな時に関数で書いて
どんな時にメソッドで書くべきなんだろう?

Step 1. 特定のクラスに限定されているかでクラスを設計する。

答え: まずは、次のような理解でいいかなと思います。

  • メソッド
    特定のクラスに限定された操作

  • 関数
    特定のクラスに限定されない操作

Pythonの関数とメソッドの違いについて - teratail


例えば list.sort メソッドと sorted 関数があります。 なぜ list.sort はメソッドで sorted は関数なのでしょうか?

実は sorted 関数は、for 文の in の中に書けるオブジェクならなんでも引数に取れます。例えば dict, list, str などです。 反対に list.sort メソッドは list しか引数に取れません。

lst = [3, 1, 2]
dct = {'a': 0, 'b': 1, 'c': 2}
txt = 'Hello, world!'

# sorted は何でも
sorted(dct)
sorted(txt)
sorted(lst)

# sort は list だけ
lst.sort()
lst
>>> # sorted は何でも
... sorted(dct)
['a', 'b', 'c']
>>> sorted(txt)
[' ', '!', ',', 'H', 'd', 'e', 'l', 'l', 'l', 'o', 'o', 'r', 'w']
>>> sorted(lst)
[1, 2, 3]
>>> 
>>> # sort は list だけ
... lst.sort()
>>> lst
[1, 2, 3]
>>> 

list.sort() メソッドはリストにのみ定義されています。 一方 sorted() 関数は任意のイテラブルを受け付けます。
Python HOWTO - ソート HOW TO


結果をリストに代入しなければなりませんが、list の場合は最初からあるリストに、結果を代入すればいいだけです。 一方で dict, str は、結果を保存するための新しいリストを生成しなければなりません。 その分だけメモリを消費してしまいます。

import sys

lst1 = [3, 1, 2]
lst2= sorted(lst1)

# sorted は新しいオブジェクトを生成している。
lst1 # [3, 1, 2]
lst2 # [1, 2, 3]

# その分、メモリを消費する
sys.getsizeof(lst1)  # 88
sys.getsizeof(lst2)  # 112

# なぜ要素数が同じなのに
# メモリは 88, 112 と結果が異なるのかは、わかりません。

パフォーマンスが問題となる状況では、ソートするためだけにリストのコピーを作るのは無駄が多いです。 そこで、 list.sort() は新しいリストを生成しないで、リストをソートします。
なぜ list.sort() はソートされたリストを返さないのですか? - Python よくある質問


# NG
# エラーになります。
lst = [0, 3, 1, 2]
for element in lst.sort():
    element
# OK
lst = [0, 3, 1, 2]
lst.sort()
for element in lst:
    element

このことを忘れないため、この関数はソートされたリストを返しません (ワイの注釈: list.sort が新たにリストを生成しないということを忘れないために、list.sort は None を返します。)。 こうすることで、ソートされたコピーが必要で、ソートされていないものも残しておきたいときに、 うっかり上書きしてしまうようなことがなくなります。
なぜ list.sort() はソートされたリストを返さないのですか? - Python よくある質問

Step 2. 現実世界のモデリングとしてクラス設計をする。

「メッセージ」という言葉があります。ちょっと難しいですが、 頭の片隅にいれて置くと、オブジェクト指向でコーディングをする上で、 関数とメソッドを書き分ける上で、いつか理解の助けになるかもしれません。

この記事は、古くかつ Java を題材にしていますが、とてもいい記事だと思います。

オブジェクト指向は、基本的に「現実世界をモデリングして、 そのモデルのコードを書いたらプログラムができる」ということを目指して作られたものです。 (実際にシステムを作るとモデルと現実世界は必ずしも一緒じゃありませんが)

ですので、オブジェクト指向を考えたお偉いさんが、現実の世界でおこっているある状態をモデル化するときに 「仕事をお願いする」というようなことを「メッセージ」という名前をつけました。
メッセージ脳の作り方 - オブジェクト脳オンライン


この記事も Java を題材にしていて、かつメッセージという言葉はでてきませんが オブジェクト指向を理解する上において、すごくいい記事だと思います。

もう一度言う。 オブジェクトはデータとそれを操作する関数をセットにしたものではない。 オブジェクトはデータエンティティではない。では何か? ... 真のオブジェクト指向の世界では、オブジェクトを生きた有機体のように扱い、 オブジェクトには生まれた日と死ぬ瞬間がある。

また、君が望むなら、アイデンティティや性質を持たせてもいい。 犬にはデータの一部(例えば体重)をくれるよう頼むことができるし、犬はその情報を返してもよい。 ただ、この犬は能動的なコンポーネントであることを忘れてはいけない。 こちらの要求に対し、何をするかは犬が決めるのだ。
Getter/Setterは悪だ。以上。 - To Be Decided

Step 3. コードの整理術としてクラス設計をする。

ただ、以下の本では、オブジェクト指向は、必ずしも現実世界のものを表現しているという訳ではなくて、 単純にコードを整理するための方法論、整理術として捉えるといいですよね的な話をしてくれています。

結局「オブジェクト指向」という難しい言葉については考えずに、関数で書いた方が読みやすければ関数で メソッドで書いた方が読みやすければメソッドで書けば、いいかなと思ったりもします。

この章では従来からしばしば行われてきた「オブジェクト指向が現実世界をそのままソフトウエアに表現する技術である」という説明について 考え直します。 結論から言うと、オブジェクト指向プログラミングの仕組みと現実世界の様子は、それなりに似ている点はあるものの、実際はかなり違うものです。

... かなり中略

筆者も長い間、コンセプトが中心だと考えていましたが、 業務分析や要求定義でオブジェクト指向のコンセプトを現実世界に当てはめ、 それをC++Javaで実装しようと試みるうちに、 このコンセプトが当初期待していたほど現実世界のモデル化やソフトウエアの設計にうまく適合しないと感じるようになりました。 このコンセプトを当てはめることにこだわりすぎて、 かえって保守しづらいアプリケーション構造にしてしまうような事例もいくつか見てきました。

(とても簡単な言葉で、 プログラミング言語オブジェクト指向にたどり着くまでの歴史、 オブジェクト指向のメリットとその限界を説明してくれています。)

オブジェクト指向でなぜ作るのか - 平澤 章

◯ 混乱したこと

Python で書いていて2つの書き方を見て混乱しました。 1つは len, str などの関数、もう1つは str.join メソッドです。

メソッドと関数の書き分けをどうすればいいのかわからなくなりました。 当時の経緯を振り返ることによって、疑問が解けるわけではないのですが、のぞいて見たいと思います。

1. メソッドではなく関数が採用された例

ただ、もし、汎用的な処理でかつ、オブジェクト指向である必要がないならば、メソッドよりも、 モジュールの中で関数として定義してしまった方が、望ましいという雰囲気、感覚を感じたりもします。

# (本物) 
len(obj)
# (偽物)オブジェクト指向なら、メソッドで統一してほしいのになんで?
obj.len()


2. 関数ではなくメソッドが採用された例

ただ、もし、汎用性の低い処理であるならば、たとえ関数として書けたとしても、 メソッドとしてクラスの中に格納してモジュールを整理してしまった方が良さそうにも感じます。

# (本物) 
', '.join(['Hello', 'wolrd!'])
# (偽物)上は読み辛い。join こそ、組込関数にしてほしいのになんで?
join(['Hello', 'world'], ', ')




4. メソッドを変更してみる。

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

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

# Step 1:
#     new_change_name 関数を定義しました。
def new_change_name(self):
    self.name = 'バラライカ'

# Step 2: 
#     正 クラスオブジェクトの属性に代入する
#        GirlFriend.change_name = new_change_name
#     誤 インスタンスオブジェクトの属性ではない。
#        girl_friend1.change_name = new_change_name
GirlFriend.change_name = new_change_name

# Step 3:
#     インスタンスオブジェクトのメソッドは変更される。
girl_friend = GirlFriend()
girl_friend.change_name()
print(girl_friend.name)  # バラライカ


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

◯ ポイント

Python の関数は、再利用性が高いです。

このようにして、関数をクラスオブジェクトの属性に代入するだけで、メソッドとして利用できます。 関数は、関数として利用したり、あるいは複数の異なるクラスのメソッドとして共有して利用したりすることができます。

def change_name(person, name):
    person.name = name

class BoyFriend(object):
    change_name = change_name

boy_friend = BoyFriend()

# メソッド
boy_friend.change_name('やる夫')

# 関数
change_name(boy_friend, 'やらない夫')


疑問: クラスオブジェクトって何?

さっきからクラスオブジェクト、クラスオブジェクトと連呼していますが、これは何者でしょうか? 実は、この記事は以下の記事からの続きになります。 クラス、クラスオブジェクトとは、何かについて説明しています。 この先を理解するには、ざっくりとでも押さえておきたい知識です。



5. 疑問: なんで明示的に self を書くの?

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

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


「暗黙的に 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 に関数を呼び出してきた
# インスタンスオブジェクトを代入するような設計」がされているなら。
class A:
    message = "Hello, world!"
    def f():
        print(self.message)

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

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

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


super クラスから呼び出せばいいじゃんという話があるかもしれません。 できない訳ではないと思いますが、 ただ、親クラスの関数 A.f を参照できなくなるため super クラスを実装することそのものが、複雑になってしまいます。

# self を省略しないなら
class B(A):
    def g(self):        
        # super().f() と同じ動作をする
        # Python のコードは簡単に書ける。
        super().f()
        A.f(self)
# 「暗黙的に self に関数を呼び出してきた
# インスタンスオブジェクトを代入するような設計」がされているなら。

class B(A):
    def f():
        # super().f() と同じ動作をする
        # Python のコードは簡単に書けない。
        super().f()
        super(A, self).f()
        ???

        # -> super クラスを実装するために
        #    特別な機能を新しく作らないといけない


super クラスは、親クラスを参照するための機能ではありません。 親クラスを参照するには、A.f(self) とすればいいだけです。

では super クラスは何をしているのでしょうか? ひし形継承した時に2回メソッドを呼び出すのを避けるための機能です。 super については、この記事が一番わかりやすいです。

super を使うメリットは、コンストラクタのような クラス階層全体にまたがるメソッドチェーンを、 ダイヤモンド型の多重継承を行った場合でも実現できることである。
Python の super のメリットとデメリット - methaneのブログ


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

ここにとても明快な論拠がある。"self"を明示的に引数として記述することによって, 以下の2つの関数呼び出しが論理的に同じであることを補強できる,というのである。 つまり,メソッドfooはクラスCのインスタンスであることを論理的に示せるわけだ。

foo.meth(arg) == C.meth(foo, arg)

和訳 なぜPythonのメソッド引数に明示的にselfと書くのか


このようにして Python では親クラスを、とても簡単に参照することができます。 しかし、言語の中身の実装なんか、どうだっていいじゃない?という意見もあるかもしれませんが、ここで Flask の開発者である Armin Ronacher(イケメン) が、Python は、文法はいいんだけど実装がやばい。と怒っている記事を紹介します。ちょっと難しいかもしれません。
私が見たい Python - The Python I Would Like To See

例 2. ある処理をメソッドとしても関数としても使いたい時

汎用的な関数を定義した時、この手の関数はメソッドとして再利用したい時があります。もし「self に関数を呼び出してきたインスタンスオブジェクトを暗黙的に代入するような設計」だと、 関数をメソッドとして再利用できなくなります。あるいは、その逆も、できなくなります。

# 「暗黙的に self に関数を呼び出してきた
# インスタンスオブジェクトを代入するような設計」がされているなら。

# 1. 関数はメソッドとして再利用できない。
def change_name(person, name):
    person.name = name

class BoyFriend(object):
    change_name = change_name

boy_friend = BoyFriend()


# 関数
change_name(boy_friend, 'やる夫')

# メソッド にはインスタンスを与えないといけない。
boy_friend.change_name(boy_friend, 'やる夫')
# 「暗黙的に self に関数を呼び出してきた
# インスタンスオブジェクトを代入するような設計」がされているなら。

# 2. メソッドは関数として再利用できない。
def change_name(name):
    self.name = name

class BoyFriend(object):
    change_name = change_name

boy_friend = BoyFriend()


# 関数 としては使えない(self に何も代入されないから)
# change_name('やる夫')

# メソッド
boy_friend.change_name('やる夫')





例 3. classmethod, staticmethod デコレータを定義する時

self が明示的に書かれていれば classmethod, staticmethod もクロージャを使えば簡単に実装できます。

def classmethod(function):
    def decorated_method(self, *args, **kwargs):
        return function(type(self), *args, **kwargs)
    return decorated_method
def staticmethod(function):
    def decorated_method(self, *args, **kwargs):
        return function(*args, **kwargs)
    return decorated_method


ところが、もし「self に関数を呼び出してきたインスタンスオブジェクトあるいはモジュールを暗黙的に代入するような設計」をされた時、これと同じ動作をどうやって実装しましょう?

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

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

疑問: なんで self って書くことが不自然に感じるんだろう?

答え: メソッドを定義していると思っているから

自分も 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


Pyhton は基本的には(広い意味での)関数型言語で、オブジェクト指向はハッシュ表を使って後付したものです。 この点、もともとオブジェクト指向言語として設計された C++, Java, Ruby などとは違います。

Python は手続きの定義を、関数定義とメソッド定義に分ける代わりに、 全てを関数定義にし、メソッド定義として使う場合は第一引数をインスタンスに割り当てるという 約束事を導入しました。

このようにしたのは、関数型言語は、オブジェクト指向言語より抽象性が高いという信念でしょう。 Python は関数型言語 Haskell から多大な影響を受けているいわれています。 実際、[code 2] で示したように関数型言語を使えばオブジェクト指向は簡単に実装できます。
Python のクラスシステム


上の文章は、とても、とても勉強になったのですが、若干の修正が必要で Python はメソッドに比べて関数をとても重視していますが、Python は命令型言語ではなく関数型言語に重きをおいているという訳では、どうやらなさそうです。

(Guido van Rossum) は Python が関数型言語から強く影響を受けたことがあるとは一度も考えたことはなかったが、 そのように言ったり思ったりする人はいるようである。私がそれまでに慣れ親しんできた言語は、C言語やAlgol 68のような命令型言語である。
Python の"関数型"の機能の起源 - The history of Python

関数型プログラミングとは Referential Transparency を徹底的に追求するプログラミングです ... 後略
megumin1 氏のブクマ

◯ 関数と辞書でブジェクト指向

上で引用した記事 Python のクラスシステム では、関数(クロージャ)と辞書の2つを組み合わせれば、オブジェクト指向と同じようなことを表現できることを紹介しています。インスタンスメソッドオブジェクトを関数(クロージャ)で、クラスオブジェクトやインスタンスオブジェクトを辞書で、それぞれ代用してオブジェクト指向を表現しています。
emulating_oop.py - GitHubGist(自分にもわかるように書き直しました。)

クロージャとメソッドに、なんの関係があるんや?と思われた方は、Effective Python の 「3 章 クラスと継承 - 項目23: 単純なインターフェイスにはクラスの代わりに関数を使う」を読んでみてください。メソッド、クロージャの関係が近しいことを理解できるだけでなく、メソッド、クロージャの実践的な使い方を紹介してくれています。

属性と辞書に、なんの関係があるんや?と思われた方は、以下のコードを見てください。実際に Python の属性、さらには変数までもが(即ち名前空間そのものが)、辞書で実装されていたりします。

# var = 0
locals().update({'var': 0})
print(var)  # 0


# obj.attr = 1
class Cls(object):
    pass


obj = Cls()
obj.__dict__.update({'obj_attr': 1})
print(obj.obj_attr)  # 1

名前空間 (namespace) とは、名前からオブジェクトへの対応付け (mapping) です。ほとんどの名前空間は、現状では Python の辞書として実装されています
9.2. Python のスコープと名前空間 - Python チュートリアル


◯ 関数に傾きがちな Python

どうやら関数というのは、とても汎用的な表現のようです。Python は、できるのであれば、関数という枠の中に収めようとしている気配を感じます。メソッドの定義以外にも、そのようなものが2つあります。

  1. メソッド定義文を導入せず、都度 self 書いてでも、関数定義文で書くメソッド定義

  2. ジェネレータ関数定義文を導入せず、関数とは動作が違うにもかかわらず、関数定義文で書くジェネレータ関数定義
    慈悲深き終身独裁官による判決 - PEP 255 Simple Generators

  3. メソッドを定義してから、それを関数で呼び出すという若干二度手間を踏む感のある len, str, repr, abs といた処理
    Python の len はなんでメソッドではなく関数なの?


◯ まとめ

「なぜ self を書くのか?」という疑問は、「なぜ関数定義文でメソッドを定義するのか?」に言い換えることができますし、おそらくそっちの方が考えやすいかなと思います。さっそく、質問の文章を変えて、もう一度、考え直してみたいと思います。

5. 疑問: なんで関数定義文でメソッドを定義するの?

答え: 関数定義文は、メソッド定義文よりも汎用的な表現だから

もう一度、最初の疑問に戻ります。メソッッドを定義する度に self をわざわざ書くのは可読性という実用性を損ねている気がします。関数という一貫性を重視する必要性は、あったのでしょうか?

Special cases aren't special enough to break the rules.
特別なケースは、一貫性を破るほど特別ではない。
Although practicality beats purity.
けれども実用性は、一貫性に勝る。
PEP 20 -- The Zen of Python


Python の開発者たちが出した答えは "はい" です。そして Ruby の開発者たちが出した答えは "いいえ" です。ここで Ruby を例にとって見てみたいと思います。自分は Ruby を書いたことが無いので、間違っていたらご容赦ください。

◯ 実用性と一貫性, Ruby と Python

関数をメソッドとして共有したり、親クラスのメソッドを読んだり、クラスメソッド, スタティックメソッドするときでも、 Python は "新しい機能を追加することなく" 一貫性の高いコードでこれを実装することができます。

Ruby でやってもらおうとするには、新しい機能が必要になってきます。サンプルコードを比較しながら見ていきたいと思います。実際に読む前に簡単に 3 点だけお伝えしておきます。

0. Ruby のコードを読む前に...

1. Ruby のシェル, IRB(Interactive Ruby) を起動するには irb を実行します。下記のサンプルコードも、シェルにコピペで実行できるようになっています。

$ irb
irb(main):001:0> 


2. 全然、よくわかってないのですが、面白いのでサンプルコードでは return は、全部省略しました。 return 文を省略すると最後に評価された式の値が返されます。

Ruby ではメソッドの最後の return は使っても使わなくても全く同じ 意味に書ける。... 理由は、まず簡潔さ。それから「Ruby ではすべての式が値を持つ」と いう基本思想を適切に表現するからである。 if も while もイテレータ もメソッドも同様に値を持つ。ならば書式も全部同じにするのが適切で ある。 あらゆる式の記述の中にオブジェクトの流れを見るのだ。
return - Ruby のコーディングスタイル


3. インデントはスペース 2 文字です。 Ruby の正式なコーディング規約はありませんが、 2 文字であることが多いそうです。

Rubyの正式なコーディング規約はありません。
コーディング規約


ちなみに Python では PEP 8 でインデントが 4 文字に定められている理由は、ネストを深くして欲しくないからだと個人的に思っています。

Flat is better than nested.
ネストは浅い方が良い
PEP 20 - The Zen of Python

例 1. 親クラスのメソッドを指定して呼ぶ

Ruby では alias を追加して対応しています。 Python では、機能を追加しなくても、属性参照で対応することができます。

class A
  def f
    'hello'
  end
end
 
class B < A
  # alias という機能が必要
  alias :super_f :f
  def g
    super_f
  end
end
  
o = B.new
o.g
class A(object):
    def f(self):
        return 'hello'

class B(A):
    def g(self):
        # 属性参照だけ
        return A.f(self)

o = B()
o.g()


もし super_method を使うなら class C は次のような具合になります。

class B < A
  def g
    # super_method という機能が必要
    method(:f).super_method.call
  end
end
class B(A):
    def f(self):
        # 属性参照だけ
        return A.f(self)

        # Python にも super がありますが
        # これは、ひし形継承されていた時に
        # 祖先クラスのメソッドが
        # 2度呼び出されるのを避けるためのものです

        # Ruby の super_method と
        # Python の super は目的としている機能が少し違います。
        return super().f()

親クラスの別のメソッドを呼ぶ - Qiita

例 2. ある処理をメソッドとしても関数としても使いたい時

Ruby のやり方が見つからない.. orz

例 3. スタティックメソッド、クラスメソッド

スタティックメソッド
Ruby では、スタティックメソッドがありません。 Python には、スタティックメソッドがあります。


クラスメソッド
Ruby では、クラスメソッドを特異メソッドで対応しています。 Python では、デコレータで対応しています。

特異メソッドとは?単一のオブジェクトに特化したメソッドのことらしいです。
特異メソッドについて -Qiita


Ruby では、クラスメソッドはクラスオブジェクトからアクセスしなければなりません。 Python では、クラスメソッドにインスタンスオブジェクトから直接参照できます。

class Hoge
  # def.self.メソッド名 という機能が必要
  def self.class_name
    self.name
  end
end

hoge = Hoge.new

# クラスオブジェクトから呼び出し
Hoge.class_name

# インスタンスオブジェクトから呼び出し → できない
hoge.class_name
class Hoge(object):
    @classmethod
    def class_name(cls):
        return cls.__name__

hoge = Hoge()

# クラスオブジェクトから呼び出し
Hoge.class_name()

# インスタンスオブジェクトから直接呼び出し → できる
hoge.class_name()

Ruby のクラスメソッド定義まとめ - Qiita
Rubyのクラスメソッドがリファクタリングに抵抗する理由


ただ Python では、デコレータと言う機能がなくても、実装できます。

class Hoge(object):
    def class_name(cls):
        return cls.__name__
    
    # 代入だけ(クロージャ)
    class_name = classmethod(class_name)

◯ Python は Ruby よりも優れているのか?

答え: そういうわけではないかなと思ったりします。

Ruby に比べて Python の方が一貫性のある書き方ができそうです。ここで言う一貫性とは、新しい機能を追加しなくても3つの機能を実装できました、と言うことです。

だからと言って、Python の方が優れているとは言えないかなと思ったりします。実際 Python 以外で関数定義文でメソッドを定義する言語をあまり見たことがないので、必要性が薄かったりするのかなと思ったりもします。

第1の理由は、頻度の多寡です。上で見た3つのことをしようとする機会がそんなに多くありません。反対に self を書く機会は、ゲシュタルト崩壊を起こしかねないくらい、とても多いです。

第2の理由は、影響の大小です。一貫性のある書き方ではできませんが、それでもその機能が実装できない訳ではないからです。あるいはなかったとしても、そこまで困りません。影響について、3つの例を振り返って見ます。

1. 親クラスのメソッドを指定して呼ぶ

この点において Python は Ruby よりも簡単に操作もできますし、 きっと実際に中身の実装もシンプルなものだと思います。

ただ、本来 実装による継承は避けられるべき と言うことを考えると、 実装による継承をしているということ自体、あまりよくないのかもしれません。

2. ある処理をメソッドとしても関数としても使いたい時

そんなことがしたい時は、恐らくそんなに無いかなと思います。

3. スタティックメソッド、クラスメソッド

あった方が可読性は良くなりますが無くても困らないかなと思います。 クラスメソッドについてはクラスオブジェクトを取得して self を使わなければ、それでことが済みます。 スタティックメソッドは self を使わなければ、それでことが済みます。 インスタンスを参照できてしまうので、正確には違いますが。

class Hoge
  def class_method
    # cls を取得して 
    # self を参照しなければいい
    cls = self.class
    cls
  end

  def static_method
    # self を参照しなければいい
    'Hello, world!'
  end
end

hoge = Hoge.new
hoge.class_method
hoge.static_method


Ruby は Python に比べて、一貫性よりも実用性を大事にしているのかもしれません。例えば Python の join は、なんで関数ではなくメソッドなの? では str.join メソッドについて見てきました。Python では実用性(可読性)よりも、実装の一貫性が優先されていました。Ruby では実用性(可読性)が、一貫性よりも優先されて実装されています。

# Python 読みにくい
", ".join(["hello", "world"])
# Ruby 読みやすい
["hello", "world"].join(", ")


一貫性 purity を大事にした Python がデータサイエンス系の分野で好まれて、 実用性 practicality を大事にした Ruby がウェブ系の分野で発展を遂げたのは、 ある意味そういうところにあるのかなと思ったりもします。
なぜ機械学習に Python が選ばれるのか - Qiita

Python の一貫性 purity は美しさであり、 Ruby の実用性 practicality は楽しさかなと思ったりもします。自由な楽しさと言うのは新しいサービスを生み出すこととマッチしているような気もします。

Rubyの価値は「楽しいプログラミング」
Ruby誕生から25年、そしてこれからのRubyは––まつもとゆきひろ氏が描く未来

Rails is the most popular web framework in the bay area by job openings.
2018/06/17 Demand for Ruby on Rails is Still Huge


メソッド定義を関数で定義することによって最も利益が得られるのは、親クラスの関数を参照する機能を実装する時です。 しかし、どうやら実装による継承は避けた方が望ましいということを考えると (継承よりも合成) 、self を書くメリットは、すなわち関数定義文でメソッドを定義するメリットは、もしかしたら薄れてしまっているかもしれません。


◯ まとめ

Python では「なぜ、第一引数に明示的に self を書くのか?」は、実は「なぜ、メソッドの定義を関数定義文で行なっているのか?」に言い換えることができます。

そして、「なぜ、メソッドの定義を関数定義文で行なっているのか?」に対する答えは「関数定義文はメソッド定義文よりも汎用的な表現なので、より複雑なことをしようと思った時でも、一貫性を崩すことなく(Python 本体に新しい機能を追加しなくても)実装することができるからです。これは最終的に可読性の良さをもたらすから。」です。

Python は可読性、実用性を重視するが故に、私たちが思っている以上に、一貫性のある実装を優先しています。このことが str.join メソッドや self を明示したりすることに対して感じた違和感の原因でした。

また、何でも一貫性を重視すればいいというものではなく、その辺りのバランスは物によりけりかなとも思います。Ruby はウェブ系で、 Python はデータサイエンス系で発展しました。





中国 甘粛省





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


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

# これが呼び出されると
girl_friend.change_name('岩倉玲音') 

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

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


◯ 復習

メソッドを変更したときの挙動を、ちょっと復習してみます。

#
# こんなクラスを作りました。
class GirlFriend(object):    
    def change_name(self, name):
        self.name = name

girl_friend_a = GirlFriend()
girl_friend_a.change_name('サーバルちゃん')
print(girl_friend_a.name)  # サーバルちゃん

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


#
# メソッドを変更します。
def new_change_name(self):
    self.name = 'バラライカ'

GirlFriend.change_name = new_change_name


# メソッドを変更した後にインスタンス化したオブジェクトの
# メソッドは変更できています。
girl_friend_c = GirlFriend()
girl_friend_c.change_name()
print(girl_friend_c.name)  # 'バラライカ'

# メソッドを変更する前にインスタンス化したオブジェクトの
# メソッドも変更できています。
girl_friend_a.change_name()
girl_friend_b.change_name()
print(girl_friend_a.name)  # 'バラライカ'
print(girl_friend_b.name)  # 'バラライカ'

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

答え: メソッドです。


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

# Step1. インスタンスオブジェクトの生成
change_name_method = girl_friend.change_name

# Step2. メソッドの呼び出し
change_name_method('岩倉玲音')

# Step3. 実際には自動的にこれが呼びされている。
change_name_method.__func__(
    change_name_method.__self__, '岩倉玲音')

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

インスタンスメソッドオブジェクトが呼び出される際、
根底にある関数 (__func__) が呼び出されます。
このとき、クラスインスタンス (__self__)
引数リストの先頭に挿入されます。
呼び出し可能型 (callable type) > インスタンスメソッド

インスタンスを通してメソッド (クラスの名前空間内で定義された関数) にアクセスすると、 特殊なオブジェクトが得られます。それは束縛メソッド (bound method) オブジェクトで、 インスタンスメソッド (instance method) とも呼ばれます。 呼び出された時、引数リストに self 引数が追加されます。 束縛メソッドには 2 つの特殊読み出し専用属性があります。 m.__self__ はそのメソッドが操作するオブジェクトで、 m.__func__ はそのメソッドを実装している関数です。 m(arg-1, arg-2, ..., arg-n) の呼び出しは、 m.__func__(m.__self__, arg-1, arg-2, ..., arg-n) の呼び出しと完全に等価です。
4.12.4. メソッド - Python 標準ライブラリ


ちなみに Python 2 の頃はクラスオブジェクトの関数は unbound method と呼ばれていました。Python 3 では単純に関数 function になりました。bound 束縛しているのは self を指しています。

>>> # Python 3
>>> girl_friend.change_name
<bound method GirlFriend.change_name ...>
>>> 
>>> GirlFriend.change_name
<function GirlFriend.change_name
>>> # Python 2
>>> girl_friend.change_name
<bound method GirlFriend.change_name ...>
>>> 
>>> GirlFriend.change_name
<unbound method GirlFriend.change_name>
>>>


Python 2 の unbound method は、そのクラスのインスタンスだけが、第一引数に代入できるように限定された method (関数)です。しかし、特定のクラスのインスタンスに限定しても、コーディングをする上であまり意味がないことがわかり、Python 3 では削除されました。

Python3000 では、非束縛メソッドの考え方が削除され、"A.spam" という式は、通常の関数オブジェクトを返すようになった。結局、最初の引数をAのインスタンスの限定しても、問題の分析の役に立つことはあまりない、ということになったのである。逆に高度な使い方を使用とした場合に邪魔になることも多かったからである。この高度な使い方というのは「duck typing self」という適切な名前で呼ばれている。
すべてをファーストクラスに - The History of Python.jp

まとめ

オブジェクトからメソッドを呼び出すと、実は関数からメソッドへの変換が行われます。生成されたメソッドの属性には、呼び出したオブジェクトと関数が代入されています。

では、誰がインスタンスメソッドオブジェクトを生成しているのでしょうか?答えは、ディスクリプタです。いきなりディスクリプタという抽象的な機能について、触れてもちょっと厳しいかなと思うので、もう少し実際にメソッドを操作して、メソッドが生成される動作をなんとなく把握しておきたいと思います。

# 参照するだけでインスタンスメソッドオブジェクトが生成される。
change_name_method = girl_friend.change_name

7. メソッドの呼び出しを高速化してみる。

最初からメソッドを変数に代入しておくと少しだけ処理を速くできます。

なぜ、速くなるかと言うと、属性を参照するたびに実行される関数からメソッドへの変換という処理を、省略できるからです。

$ # 変数の参照
$ python -m timeit -s "pop=[i for i in range(10000000)].pop" "pop()"
10000000 loops, best of 3: 0.0792 usec per loop
$
$ # メソッドの参照
$ python -m timeit -s "lst=[i for i in range(10000000)]" "lst.pop()"
10000000 loops, best of 3: 0.115 usec per loop
$

2. 変数の参照と属性の参照速度の比較 - Python を高速化したい


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


可読性が落ちるので使っていいのか疑問だったのですが copy モジュールの中で、このような実装をしているところを見かけました。このコードは list を deepcopy するコードです。

def _deepcopy_list(x, memo, deepcopy=deepcopy):
    y = []
    memo[id(x)] = y
    append = y.append
    for a in x:
        append(deepcopy(a, memo))
    return y


ただ、同じ copy モジュールの中でもこのような書き方がなされていないところもあります。実行時間を計測して、どのくらい効果が見込めるのかと、可読性を天秤にかける必要があるかなと思います。

deepcopy は遅いんやって stackoverflow で文句を言われてるのを見かけました。自分も昔使っていて、なんか重いなって印象がありました。履歴を見たら割と最近 2016/03/16 に、このような書き方に変更されていたので、天秤をかけた結果の苦肉の策かなと思ったりもします。
deepcopy() is extremely slow Ask Question - stackoverflow

◯ リスト内包表記が速い理由

また、一般に for 文よりもリスト内包表記が速いです。これは append メソッドを呼び出す必要がなくなるからです。

"""乱数のリストを生成する"""
import random

random_list1 = [random.randint(0, 999) for _ in range(1000)]

random_list2 = []
for i in range(1000):
    random_list2.append(random.randomint(0, 999))

5. for 文と list 内包表記の速度の比較 - Python を高速化したい


8. 疑問: 関数からメソッドに変換って何をしているの?
「生成」それとも「切替」?

答え: 新しいインスタンスメソッドオブジェクトが、その都度「生成」されています(インスタンス化されています)。

キャッシュを作って、インスタンスメソッドオブジェクトに参照先を「切替」るだけというようなことはしていません。メソッドの参照は、どうやら見た目より重たい処理のようです。

なぜ、調べたかというと、マニュアルで「変換」という言葉が使われていたので、「切替」なのか「生成」なのか気になったからです。

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

◯ identity

これを調べるためにインスタンスメソッドオブジェクトを生成した時に identity が、どのように変化するかを通して確認しました。

identity というのは、オブジェクトが持っている番号です。id 関数で調べることができます。以下の記事は identity について説明せいて頂いております。この先を理解するには、ざっくりと押さえておきたい知識です。

1. 参照するだけ

identity は変化しませんでした。

id(girl_friend.change_name)
id(girl_friend.change_name)
>>> id(girl_friend.change_name)
4533991496
>>>
>>> # identity は変化しない
>>> id(girl_friend.change_name)
4533991496
>>> 


このことから、インスタンスメソッドオブジェクトは呼び出されているたびに、生成されてないと言えるでしょうか?

identity が同じだったということは2つの可能性があります。 1つ目は、同じインスタンスメソッドオブジェクトを使いまわしている可能性。 2つ目は、インスタンスメソッドオブジェクトを生成して破棄して、 同じ identity を使ってインスタンスメソッドオブジェクトを生成している可能性。 もう少し追いかけてみたいと思います。

2. 属性をローカル変数に代入しておき、そのローカル変数を呼び出すようにする

identity が変化しました。

新しいインスタンスオブジェクトが生成さたことが、わかります。 どうやら呼び出される度にインスタンスオブジェクトが生成されていそうです。 また、オブジェクトの生成、破棄は、変数への代入と何らかの関係がありあそうです。

id(girl_friend.change_name)

# 変数にインスタンスメソッドオブジェクトを代入すると...
new_change_name_method = girl_friend.change_name

id(girl_friend.change_name)

id(new_change_name_method)
>>> id(girl_friend.change_name)
4438100040
>>> 
>>> # 変数にインスタンスメソッドオブジェクトを代入しておくと...
... new_change_name_method = girl_friend.change_name
>>> 
>>> # 次に呼び出した時には
>>> #  が変化する。
>>> id(girl_friend.change_name)
4438100104
>>> 
>>> id(new_change_name_method)
4438100040
>>> 

3. 別のインスタンスメソッドオブジェクトを変数に保存する。

identity が変化しました。

どうやら呼び出される度にインスタンスオブジェクトが生成されているのは、ほぼ確実そうです。

id(girl_friend.change_name)

class C(object):
    def f(self):
        pass

o = C()
m = o.f
id(m)

id(girl_friend.change_name)
>>> id(girl_friend.change_name)
4350253192
>>> 
>>> class C(object):
...     def f(self):
...         pass
... 
>>> o = C()
>>> m = o.f
>>> id(m)
4350253192
>>> 
>>> id(girl_friend.change_name)
4351437192
>>> 

◯ ガベレージコレクション

なぜ、1 では、インスタンスメソッドオブジェクトが、インスタンス化されたあとにすぐに削除されたのでしょうか? それは、どの変数にも代入されていなかったので、もう使われることはないオブジェクトだと判断されたからです。

使わなくなったオブジェクトが保存されたメモリから解放することは、とても重要です。 なぜならオブジェクトはメモリを占有してしまうからです。 使わなくなったオブジェクトを回収する機能をガベレージコレクションと言います。

f:id:domodomodomo:20180409203421p:plain f:id:domodomodomo:20180409203432p:plain
使わなくなったオブジェクトは
メモリから片付ける。


1. 参照カウント

CPython では、参照カウントを使ってガベレージコレクションを実装しています。オブジェクトが、変数あるいは属性に代入されている数を保存しておきます。例えば、オブジェクトが変数 a に代入されれば、参照カウントを1つ増やします。また、変数 a を参照できなくなると、参照カウントを -1 減らします。参照カウントが 0 になると、オブジェクトが参照できなくなった、今後そのオブジェクトを利用することは無くなった、と判断して、オブジェクトを破棄します。

参照カウント法 - Python/C API リファレンスマニュアル
今日の計算機は有限の (しばしば非常に限られた) メモリサイズしか持たないので、参照カウントは重要な概念です; 参照カウントは、あるオブジェクトに対して参照を行っている場所が何箇所あるかを数える値です。... 中略 ... あるオブジェクトの参照カウントがゼロになると、そのオブジェクトは解放されます。... 後略


1, 2 では同じ identity を使ってインスタンス化→削除→インスタンス化→削除を繰り返していました。 なんでインスタンスメソッドオブジェクトの identity が変化しなかったのか?というのは、ひとつ疑問です。 おそらくそういうメモリの確保の実装をしているからだと思います
ヒープとスタック - ユークエスト株式会社
malloc - Wikipedia

ほとんどの Python は C 言語で実装されています。C 言語で実装された Python を CPython と言います。 CPython の実装では は、メモリのアドレスです。 データを保存する場所であるメモリには、住所のような番号が割り振られています。 その住所のような番号をアドレスと言います。 この説明では、全くピンと来ないと思うので C言語, ポインタ などで検索するといいかもしれません。

1, 2, では、アドレスが 4533991496 のメモリを使って、インスタンス化してはガベレージコレクションで 4533991496 のメモリを解放して、また解放されたアドレスが 4533991496 のメモリを使ってインスタンス化してはガベレージコレクションでアドレスが 4533991496 のメモリを解放するという動作を繰り返していました。

2. 循環参照ガベージコレクション

ちなみに参照カウント法では、相互に参照し合うようなケースではうまく動作しません。 そのような循環参照を検知するための別の GC が必要になるようです。

参照カウント法 - Wikipedia
オブジェクト同士が互いに参照しあうなど、参照が循環している場合、 参照カウントが0にならないためにオブジェクトが破棄されないという問題がある。(詳しくは後述)

参照カウント法 - Python/C API リファレンスマニュアル
言うまでもなく、互いを参照しあうオブジェクトについて問題があります; 現状では、解決策は "何もしない" です。(ワイの注記: Python の参照カウント法が、循環参照に対して何もしないのであって、Python には循環参照を捉える機構はあるようです。)

現在の CPython 実装では参照カウント(reference-counting) 方式を使っており、(オプションとして) 循環参照を行っているごみオブジェクトを遅延検出します。この実装ではほとんどのオブジェクトを到達不能になると同時に処理することができますが、循環参照を含むごみオブジェクトの収集が確実に行われるよう保証しているわけではありません。
3.1. オブジェクト、値、および型 - Python 言語リファレンス


循環参照 GC の話をしてくれています。 死ぬほどわかりやすく書いてくれていると思うのですが、自分はまだ 1 mm も理解できていません。
Compact PyGC_HEAD

◯ まとめ

メソッドは呼び出されるたびにインスタンス化していました。これによって動的にクラスオブジェクトの関数が変更されると、すぐにメソッドも変更されるようになっています。

9. 疑問: 誰が関数からメソッドに変換しているの?

答え: ディスクリプタ

# これが呼び出された時に
girl_friend.change_name('岩倉玲音') 

# これが呼び出されています
girl_friend.__class__.__dict__['change_name'].__get__(girl_friend, type(girl_friend))('サーバルちゃん')




コードを書く人が、関数を定義する。そして、関数が代入されたクラスオブジェクトの属性を参照されると、誰かが、関数の第一引数に関数を呼び出してきたインスタンスオブジェクトを代入して関数を実行する。メソッドを呼び出した時に、一体誰が、このような動作を実装しているのでしょうか?

ディスクリプタを理解すると、公式マニュアルの説明が理解できて、その動作がなんとなく把握することができます。

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


ディスクリプタについて、ごく手短に説明すると、クラス変数が参照された時に、もしそのクラス変数に束縛されたオブジェクトが __get__ メソッドを持っていると __get__ メソッドが呼び出されています。

# インタラクティブシェルに
# コピペして実行して見てください。
class C(object):
    def __get__(self, obj, objtype):
        print(self)
        print(obj)
        print(objtype)
        return 'Hello, world!'

class D(object):
    c = C()

d = D()

# d.c 属性を参照しただけで
# C.__get__ メソッドが実行されます。
print(d.c)
>>> # d.c 属性を参照しただけで
... # C.__get__ メソッドが実行されます。
... print(d.c)
<__main__.C object at 0x10b3c15f8>
<__main__.D object at 0x10b3c15c0>
<class '__main__.D'>
Hello, world!
>>>


関数オブジェクト function の __get__ メソッドを次のように定義することで、メソッドを簡単に実装できます。ディスクリプタとクロージャを組み合わせただけです。このコードは、疑似コードです。

class function(object):

    ...

    def __get__(self, obj, objtype):
        def method(*args, **kwargs):
            return self(obj, *args, **kwargs)
        return method


本当に疑似コードと同じような動作をするのでしょうか?ディスクリプタを直接操作して確認してみました。 girl_friend.change_name が参照されると function.__get__ メソッドが呼ばれます。 引数 self には関数オブジェクト GirlFriend.change_name が 引数 obj にはメソッドを呼び出したオブジェクト girl_friend が代入されます。

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

girl_friend = GirlFriend()

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

# 2) ディスクリプタでの呼び出し方
girl_friend.__class__.__dict__['change_name'].__get__(girl_friend, type(girl_friend))('サーバルちゃん')
print(girl_friend.name)


Python は、関数とディスクリプタを組み合わせて、メソッドを実装しました。Python は、ごっちゃにひとまとめにしたメソッド定義文を定めて、メソッドを実装はしませんでした。

Complex is better than complicated.
ごっちゃにひとまとめした実装するよりも、複数のパーツを組み合わて実装する方が良い。
Simple is better than complex.
複数のパーツを組み合わて実装するよりも、各パーツをバラバラに実装する方が良い。(複数のパーツを組み合わせて1つのものを作って1つのものに同時に複数のことをさせるよりも、複数のパーツをバラバラして1つのパーツには1つのことをさせる方が良い。)
PEP 20 -- The Zen of Python
プログラマが持つべき心構え (The Zen of Python) - Qiita


メソッド呼び出し以外のディスクリプタの用途としては、Java で言う所の getter とか setter を書かずに、直接、属性を参照したり属性を代入するだけで済ませることができるようになります。身近な具体例としては Django の model で使われている気配があります。
Getter/Setterは悪だ。以上。 - To Be Decided

ディスクリプタの用途だけを手短に述べました。こんな説明では "何言ってんだ、お前?" みたいな気分かもしれません。ディスクリプタを理解するために、自分は Effective Python の 「4 章 メタクラスと属性」が、わかりやすいです(理解したとは言ってない)。

特に使用例を示してくれているので、かなりありがたかったです。1, 2, 3 章と 4 章は関係ないので、 4 章だけ読むことができます。この 30 ページの 4 章のためだけに 3,000 円以上を払う価値は十分にあると思います。

まとめ

後編で言いたかったことは、次の2つのことだけです。


  1. メソッドの定義は、関数定義文で書いた方がいろんなことができます。
  2. 関数からメソッドへの変換はディスクリプタが使われています。

たった2つのことだけですが、かなりの労力を費やしてしまいました。 こんなに長い文章に付き合っていただいた方が、もしいらっしゃったなら光栄に思います。 ありがとうございました。