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





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



◯ メソッドの動作

実はメソッド 1 が呼び出されると関数 2 に変換されて実行されています。

# 1) メソッドが呼び出されると
girl_friend.change_name('岩倉玲音') 
# 2) 関数に変換されて実行される。
GirlFriend.change_name(girl_friend, '岩倉玲音') 

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



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

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

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



◯ GirlFriend クラス

GirlFriend クラスは次のように定義されています。
この後も引き続きこのクラスを使います。

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)  # サーバルちゃん

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

答え: 可読性がよくなるから


もし、自動的に代入してくれなかった場合、こういう書き方になります。2回も girl_friend を書いていて冗長です。

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


そこで、自動的に第一引数に自分を呼び出したオブジェクトを代入してもらった方が、読みやすいコードになります。

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









こんな文章を読んで何の意味があるのでしょうか?この文章は、メソッドの動作や、なぜ self を書くのかなどの設計背景を理解して、最終的に、ディスクリプタに理解を繋げることを目的にしています。



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

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

次の3つの操作を通して比較して見たいと思います。

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

◯ 関数

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

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

# 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
>>>


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

実は、この記事は以下の記事からの続きになります。 クラス、クラスオブジェクトとは、何かについて説明しています。


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

答え: わからない。きっと時と場合による。

◯ 関数の方が良い例

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

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


◯ メソッドの方が良い例

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

# (本物) 
', '.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

class GirlFriend(object):
    change_name = change_name

boy_friend = BoyFriend()
girl_friend = GirlFriend()

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

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


5. 疑問: なんで self を書くの?

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

「暗黙的に 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 つ見てみます。

「暗黙的に self に関数を呼び出してきたインスタンスオブジェクトを代入するような設計」がされているなら、という仮定の話をします。ここで書かれたコードは、特に適当に読み流していただけると嬉しいです。関数が、再利用できなくなるんだなということだけ抑えていただけると幸いです。

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

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

# 「暗黙的に self に関数を呼び出してきた
# インスタンスオブジェクトを代入するような設計」がされているなら。
# self に sys.modules[__name__] が代入されてしまう
change_name('サーバルちゃん'):
# 「暗黙的に self に関数を呼び出してきた
# インスタンスオブジェクトを代入するような設計」がされているなら。
def func():
    # self に func が代入されてしまう
    change_name('サーバルちゃん'):

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

親クラスのメソッドを呼び出したい時がある。 もし「self に関数を呼び出してきたインスタンスオブジェクトを暗黙的に代入するような設計」だと、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 デコレータは、第一引数にメソッドを呼び出してきたインスタンスオブジェクトではなくクラスオブジェクトを代入します。

class C(object):
    # 第一引数にクラスオブジェクトが代入される。
    @classmethod
    def cls_method(cls):
        print(cls.__name__)

o = C()
o.cls_method()  # C


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

class C(object):
    # 第一引数を省略できる。
    @staticmethod
    def stc_method():
        print('Hello, world!')

o = C()
o.stc_method()  # Hello, world!


classmethodstaticmethod も組み込み関数として最初から付いてきているので自分で実装することはありませんが、このような classmethod, staticmethod は、Python のデコレータで簡単に実装することができます。

def classmethod(method):
    def decorated_method(self, *args, **kwargs):
        cls = type(self)
        return method(cls, *args, **kwargs)
    return decorated_method
def staticmethod(method):
    def decorated_method(self, *args, **kwargs):
        # self を省略してメソッドを呼び出す。
        return method(*args, **kwargs)
    return decorated_method



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

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

代替手段として 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 って書くことが不自然に感じるんだろう?

自分も 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 は、できるのであれば、関数という枠の中に収めようとしている気配を感じます。

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

2. メソッドを定義してから関数で呼び出すという若干二度手間を踏む感のある len, str, repr, abs といた処理

3. 関数とは動作が違いますが同じ関数定義文で定義するように定められた ジェネレータ関数

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

上の記事 "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 チュートリアル


疑問: self 以外の名前を使ってもいいの?

答え: 原則、使ってはいけません。

使っても動作しますが self 以外を使うことは PEP 8 という Python のコーディング規約に反します。

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


◯ まとめ

一見して self を省略して書けた方が、一貫性のある綺麗な書き方ができるようにも見えます。しかしながら、Python の関数はインスタンスオブジェクトとして扱うことができ、変数または他のインスタンスオブジェクトの属性に代入できるため、異なるクラスでメソッドとして共有したりすることもできて、使い回しがしやすいです。

そのため「self に関数を呼び出してきたインスタンスオブジェクトあるいはモジュールを暗黙的に代入する」ような制約のある設計をしてしまうと、関数を使い回しづらくなります。

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

優れたPythonプログラマはselfとlenを肯定する論拠を持っていて、 タプル値として説明できる、とかよりも、 rubyとかphpとかocamlとかも目的意識違ってて面白いよねって興味持ってる人。

— 田中ひさてる (@tanakahisateru) March 1, 2011

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


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

#
# こんなクラスを作りました。
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)  # 'バラライカ'



疑問: クラスオブジェクトの属性に関数を代入したら、配下のインスタンスオブジェクトのメソッドが全て変更されました。なぜこのような動作をしているのでしょうか?

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

# これが呼び出されると
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__)
引数リストの先頭に挿入されます。
呼び出し可能型 (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


メソッドだけで呼び出せる。

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

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

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 で文句を言われてるのを見かけました。自分も昔使っていて、なんか重いなって印象がありました。
deepcopy() is extremely slow Ask Question - stackoverflow


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

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

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

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

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

◯ identity

これを調べるためにインスタンスメソッドオブジェクトを生成した時に identity が、どのように変化するかを通して確認しました。identity というのは、オブジェクトが持っている番号です。id 関数で調べることができます。


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
>>> 
>>> # 次に呼び出した時には
>>> # identity が変化する。
>>> 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
使わなくなったオブジェクトは
メモリから片付ける。


◯ 参照カウント

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

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


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

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

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

◯ まとめ

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

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

答え: ディスクリプタ


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

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

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


関数からメソッドに変換しているコードです。公式マニュアルからの出典で、実際には C 言語で書かれているものを Python で書き直されています。関数が呼び出された時に __get__ メソッドが呼び出されます。このとき self には関数オブジェクトが、obj にはメソッドを呼び出したオブジェクトが代入されています。

そして、このコードを見ると return types.MethodType(self, obj) となっているので、メソッドが呼び出されるたびに、インスタンスが生成されているんだなというのが、なんとなくわかります。説明不足で、何を言っているか、まったくわからないと思いますが...

class Function(object):
    ...
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:
            return self
        return types.MethodType(self, obj)

デスクリプタ HowTo ガイド


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

ディスクリプタを理解するために、自分は Effective Python の 「4 章 メタクラスと属性」を読み、なんとなく理解したつもりになっています。ディスクリプタはネット上の記事だけでは、どうしても厳しかったです。

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