メソッドオブジェクト(2)

【 目次 】

ユーザ定義メソッド - メソッドと関数,クラスやインスタンスとの関係は

メソッドのメカニズムをより詳しくみていく事にしよう。

python言語をもちいてプログラマーが定義したメソッドはユーザ定義メソッドと呼ばれ、関数,クラスやインスタンスと結び付けられる。

ユーザ定義のメソッドオブジェクトは、クラスやクラスインスタンス (あるいは None) を任意の呼び出し可能オブジェクト (通常はユーザ定義関数) と結合し (combine) ます。

読み出し専用の特殊属性: im_self はクラスインスタンスオブジェクトで、 im_func は関数オブジェクトです; im_class は結合メソッド (bound method) において im_self が属しているクラスか、あるいは非結合メソッド (unbound method) において、要求されたメソッドを定義しているクラスです; __doc__ はメソッドのドキュメンテーション文字列 (im_func.__doc__ と同じ) です; __name__ はメソッドの名前 (im_func.__name__ と同じ) です; __module__ はメソッドが定義されているモジュールの名前になるか、モジュール名がない場合は None になります。 ...

クラスからユーザ定義関数オブジェクトを取得する方法でユーザ定義メソッドオブジェクトを生成すると、 im_self 属性は None になり、メソッドオブジェクトは非結合 (unbound) であるといいます。クラスのインスタンスからユーザ定義関数オブジェクトを取得する方法でユーザ定義メソッドオブジェクトを生成すると、 im_self 属性はインスタンスになり、メソッドオブジェクトは結合 (bound) であるといいます。どちらの場合も、新たなメソッドの im_class 属性は、メソッドの取得が行われたクラスになり、 im_func 属性はもとの関数オブジェクトになります。

クラスやインスタンスからクラスメソッドオブジェクトを取得する方法でユーザ定義メソッドオブジェクトを生成した場合、 im_self 属性はクラス自体 (im_class 属性と同じ) となり、 im_func 属性はクラスメソッドの根底にある関数オブジェクトになります。

結合ユーザ定義メソッドオブジェクトの呼び出しの際には、根底にある関数 (im_func) が呼び出されます。このとき、クラスインスタンス (im_self) が引数の先頭に挿入されます。例えば、関数 f() の定義が入ったクラスを C とし、 x を C のインスタンスとすると、 x.f(1) の呼び出しは C.f(x, 1) と同じになります。

ユーザ定義メソッドオブジェクトがクラスオブジェクトから派生した際、 im_self に記憶されている “クラスインスタンス” はクラス自体になります。これは、 x.f(1) や C.f(1) の呼び出しが根底にある関数を f としたときの呼び出し f(C,1) と等価になるようにするためです。

上記のドキュメントによると

ユーザ定義のメソッドオブジェクトには読み出し専用の以下の特殊属性にアクセス可能である。

im_self
クラスインスタンスオブジェクト
im_func
関数オブジェクト
im_class
結合メソッド (bound method) において im_self が属しているクラスか、あるいは非結合メソッド (unbound method) において、要求されたメソッドを定義しているクラスです

とある。

どうも意味がいまいち不明である。

im_selfにおけるクラスインスタンスオブジェクトとはクラスオブジェクトを指すのかインスタンスオブジェクトを指すのか?
そして、結合メソッド非結合メソッドとは何を指すのか?

結合メソッドと非結合メソッドとim_self、im_func、im_class

上記の記事をもとに、 あっているかどうかわからないが自分なりに下手な解説を加えてみることにしよう。

例えばクラスとクラスのインスタンスが以下のように定義されていたとする。

class MyCls(object):
    # インスタンスメソッド
    def i_method(self):
        print self.__class__

    # クラスメソッド
    @classmethod
    def c_method(cls):
        print cls.__name__

    # スタティックメソッド
    @staticmethod
    def s_method():
        print MyCls.__name__

my_inst=MyCls()

print文を使っていろいろと情報を得る事ができる。

クラスとクラスのインスタンスの値

クラスとクラスのインスタンスをprint文を使って表示すると

print MyCls
print my_inst

実行結果

<class '__main__.MyCls'>
<__main__.MyCls object at 0x0243BDB0>

すなわち、
クラスMyClsのクラスオブジェクトは<class '__main__.MyCls'>
インスタンスオブジェクトmy_instは<__main__.MyCls object at xxxxxxxxxx>
と表示される。

インスタンス経由のメソッドの値

print文でインスタンス経由のメソッドの値を表示してみると

print my_inst.i_method
print my_inst.c_method
print my_inst.s_method
<bound method MyCls.i_method of <__main__.MyCls object at 0x0243BDB0>>
<bound method type.c_method of <class '__main__.MyCls'>>
<function s_method at 0x02442430>

インスタンスメソッド,クラスメソッドともにbound methodすなわち結合メソッドである事がわかる。

そして、
インスタンスメソッドは<__main__.MyCls object at 0x0243BDB0>すなわちインスタンスオブジェクトと、
クラスメソッドは<class '__main__.MyCls'>すなわちクラスオブジェクトと
結合していると推察される。

一方、スタティックメソッドはメソッドでは無く、関数(functionクラスの)オブジェクトと表示される。

クラス経由のメソッドの値

では、クラス経由のメソッドではどうだろう。

print MyCls.i_method
print MyCls.c_method
print MyCls.s_method
<unbound method MyCls.i_method>
<bound method type.c_method of <class '__main__.MyCls'>>
<function s_method at 0x024D24B0>

クラス経由のメソッドの場合には、インスタンスメソッドはunbound methodすなわち非結合メソッド,そしてクラスメソッドはクラスオブジェクトと結合したメソッドである事がわかる。

im_class、im_func、im_selfの値についてもそれぞれ調べてみよう。

im_self属性の値

インスタンス経由の場合

print my_inst.i_method.im_self
print my_inst.c_method.im_self
<__main__.MyCls object at 0x0267F170>
<class '__main__.MyCls'>

インスタンスメソッドはインスタンスオブジェクト,
クラスメソッドの場合はクラスオブジェクトを
指している。

これは、im_selfがメソッドの第1引数、すなわちインスタンスメソッドの場合はself(インスタンス),クラスメソッドの場合はcls(クラス)に使われるためにあるという事なのだろう。

スタティックメソッドは関数オブジェクト

ちなみに、スタティックメソッドのim_self属性を表示させようとして

print my_inst.s_method.im_self # -> エラー

を実行するとエラーになってしまう。

スタティックメソッドは関数オブジェクトになっていて、ユーザ定義メソッドという扱いになっておらず、
特殊属性im_class、im_func、im_selfを保持していないようだ。

クラス経由の場合

print MyCls.i_method.im_self
print MyCls.c_method.im_self
None
<class '__main__.MyCls'>

インスタンスメソッドはNone,
クラスメソッドの場合はクラスオブジェクト
を指している。

クラス経由では、インスタンスメソッドの引数selfが特定できないのでNoneにそしてクラスメソッドの場合は自分自身のオブジェクトを指す事になる。

im_class属性の値

インスタンス経由の場合

インスタンス経由の場合im_classは

print my_inst.i_method.im_class
print my_inst.c_method.im_class
<class '__main__.MyCls'>
<type 'type'>

すなわち、
インスタンスメソッドのim_class属性はインスタンスが属するクラスのクラスオブジェクト,
クラスメソッドのim_class属性はtype型のオブジェクト(typeクラスのクラスオブジェクト)
という事になる。

クラス経由の場合

クラス経由のメソッドのim_classはどうなっているだろう?

print MyCls.i_method.im_class
print MyCls.c_method.im_class
<class '__main__.MyCls'>
<type 'type'>

インスタンス経由のメソッドと同じ結果になる。


これらの結果より、im_classの値は
インスタンスメソッドではインスタンスオブジェクトが属するクラス,
そして
クラスメソッドではクラスオブジェクトが属するクラス(すなわちtypeクラス)
となる。

すなわち、im_classはim_selfの属するクラスを指しているという事になる。

im_func属性の値

im_funcについても調べてみよう。

print my_inst.i_method.im_func
print MyCls.i_method.im_func
print my_inst.c_method.im_func
print MyCls.c_method.im_func
<function i_method at 0x02651170>
<function i_method at 0x02651170>
<function c_method at 0x02651130>
<function c_method at 0x02651130>

インスタンスメソッド,クラスメソッドともに、インスタンス経由でも,クラス経由でも同じ関数オブジェクトを指している事がわかる。
im_func属性はメソッドのもととなっている関数オブジェクトを保持している事になる。
試しに、関数オブジェクトにはfunc_codeという特殊属性があるのでこれを使ってインスタンスメソッドのもとになる関数のソースコードを表示させて確認してみよう。

import inspect
print inspect.getsource(my_inst.i_method.im_func.func_code)

実行結果

    def i_method(self):
        print self.__class__

メソッドのもととなっている関数のソースコードが表示される。

getsource関数については

ユーザ定義メソッドについての まとめ or 結論

ごちゃごちゃしてきたので、くどくなってしまうがこれまでの結果を整理してみよう。

クラス内で定義された関数でスタティックメソッド以外のメソッド(すなわちインスタンスメソッドとクラスメソッド)はユーザ定義メソッドと呼ばれる。

スタティックメソッドは関数オブジェクト

スタティックメソッドは第1引数のselfやclsを追加してやる必要が無いので、特殊な変換が必要ない。
このため、関数オブジェクトとして扱われ、im_self、im_func、im_class属性を持たずユーザ定義メソッドの仲間には入らないようだ。
かわりにスタティックメソッドでは、func_nameなどのユーザ定義関数の特殊属性を使う事ができる。

print MyCls.s_method.func_name

実行結果

s_method


結合メソッドと非結合メソッドと特殊属性im_self,im_func,im_class

ユーザ定義メソッドつまりインスタンスメソッドとクラスメソッドは、結合メソッドオブジェクトまたは非結合メソッドオブジェクトに変換された状態で参照される。
ユーザ定義のメソッドオブジェクトには読み出し専用の以下の特殊属性にアクセス可能である。

im_func
メソッドの根底にある(もとになる)関数オブジェクトが保持される。
im_self
メソッドの第1引数-インスタンスメソッドの場合のselfやクラスメソッドの場合のcls引数の値を指す。

つまりim_selfは、インスタンスメソッドの場合は自分自身のオブジェクトを示すインスタンスオブジェクト(my_inst)を,クラスメソッドの場合は自分自身のクラスを示すクラスオブジェクト(MyCls)を指すことになる。
言い換えるとインスタンスメソッドはインスタンスオブジェクトに結合される。
そしてクラスメソッドはクラスオブジェクトと結合される。
このため、これらのメソッドを結合メソッドと呼ぶ。

ところが、ここで問題が生ずる。
クラス経由でのインスタンスメソッドの場合、クラスのインスタンスは複数存在する可能性がありクラスを指定しただけではインスタンスを特定できないのでim_selfの値としてNoneが保持される事になる。
このため、クラス経由でのインスタンスメソッドの場合にはim_selfの値がNoneになってしまうので、インスタンンスオブジェクトと結合できない。
つまりクラス経由でのインスタンスメソッドは非結合メソッドという事になる。
これは前述の結果と一致する。

インスタンスメソッドが何をしているかというと、

my_inst.i_method()

の呼び出しは

MyCls.i_method(my_inst)

あるいは、特殊属性im_funcメソッドと結合したim_selfを利用して関数呼び出しの形に変換して

method_object=my_inst.i_method
method_object.im_func(method_object.im_self)

にpython内部で変換されて呼び出されるという事だろう。
実際、この3つのコードの実行結果は一致する。

my_inst.i_method()
MyCls.i_method(my_inst)
method_object.im_func(method_object.im_self)

実行結果

<class '__main__.MyCls'>
<class '__main__.MyCls'>
<class '__main__.MyCls'>

そしてクラスメソッドの場合は、 同様に

MyCls.c_method()

の呼び出しも

method_object=MyCls.c_method
method_object.im_func(method_object.im_self)

という事になる。

つまり
特殊属性im_selfやim_funcを保持することによって
インスタンスメソッドもクラスメソッドも同じ変換によって生成できる
ようになるという事。

im_class
インスタンス結合メソッド (bound method) において im_self が属しているクラスか、あるいは非結合メソッド (unbound method) において、要求されたメソッドを定義しているクラスです。

という事は、インスタンスメソッドの場合はインスタンスオブジェクトが属しているクラス、
すなわちMyClsが,クラスメソッドの場合はクラスオブジェクトMyClsが属しているtype型(クラス)という事になる。
すなわち、インスタンスメソッドのim_class属性はインスタンスが属するクラスのクラスオブジェクト,クラスメソッドのim_class属性はtype型のオブジェクトという事になる。

これも、前述の結果と一致している。

メソッドはディスクリプタを使って実装されている。
これについては下記のリンクを参照。

と、ここまでやっとの思いで調べてきたのに、...
python3ではunbound method という概念が無くなったらしい。

  • Python を支える技術 ディスクリプタ編 #pyconjp - Qiita

    Python 3 の場合、クラス経由でインスタンスメソッドを参照した場合は関数自体が返ってきますが、 Python 2 の場合は unbound method というものが返ってきます。関数自体を参照するには func という属性を参照する必要があります。この書き方は Python 3 だとエラーになるので、もしこういうコードがある場合 Python 3 に移植する際に注意してください。 Python 3 ではそもそも unbound method という概念がなくなりました。

ページのトップへ戻る