UP
オブジェクトの属性を操る(4) - ディスクリプタ
前のページへ
Python
次のページへ
メソッドオブジェクト(2)


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

【 目次 】

メソッドオブジェクト

メソッドもオブジェクトでありメソッドオブジェクトと呼ばれる。

  • メソッドオブジェクト - 9. クラス — Python 2.7ja1 documentation

    データ属性ではないインスタンス属性が参照された時は、そのクラスが検索されます。その名前が有効なクラス属性を表している関数オブジェクトなら、インスタンスオブジェクトと見つかった関数オブジェクト (へのポインタ) を抽象オブジェクト、すなわちメソッドオブジェクトにパックして作成します。メソッドオブジェクトが引数リストと共に呼び出されるとき、インスタンスオブジェクトと渡された引数リストから新しい引数リストを作成して、元の関数オブジェクトを新しい引数リストで呼び出します。

メソッドはクラス定義内にクラスの属性として関数のかたちで記述される。

class MyCls:
    def method(arg):
        pass

メソッドにはインスタンスメソッド,クラスメソッド,スタティックメソッドがある。

次のコードはクラス内で定義されているものが、インスタンスのメンバーかクラスのメンバーかを確認するためものである。

class MyCls:
    c_var = "class"

    # コンストラクタ
    def __init__(self):
        self.i_var = "inst"

    # インスタンスメソッド
    def i_method(self):
        pass

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

    # スタティックメソッド
    @staticmethod
    def s_method():
        pass

my_inst = MyCls()

print my_inst.__dict__
print MyCls.__dict__

実行結果

{'i_var': 'inst'}
{'c_var': 'class', '__module__': '__main__', 'c_method': <classmethod object at 0x02719ED0>, 'i_method': <function i_method at 0x027010F0>, '__doc__': None, 's_method': <staticmethod object at 0x02719F10>, '__init__': <function __init__ at 0x027010B0>}

インスタンスの辞書にはインスタンス変数のみが保持されているのに対し、クラスの辞書にはクラス変数やメソッドが保持されているのがわかる。

メソッドはクラスの属性であり(特異メソッドなどの例外を除けば)クラスに保持される。
インスタンス変数がインスタンスの属性になるのに対して、インスタンスメソッドはその名称とは異なりインスタンスの属性では無くクラスの属性になるので注意。
インスタンスメソッドはインスタンス固有のメソッドでは無く、同じクラスから生成されたクラスのインスタンスであれば共通に利用できるので、当たり前の事ではあるが。

インスタンスメソッド

インスタンスよりアクセスするメソッドとしてインスタンスメソッドがある。
インスタンスメソッドは引数selfを使ってインスタンスのメンバーを操作することができる。

pythonの特徴としてインスタンスメソッドの第1引数には必ず自分自身のインスタンスを示す引数(慣例としてselfという引数名を使う)を明示的に指定する必要がある。
javaなど,他のプログラミング言語ではこのselfはthisという名称で、暗黙の引数として省略されるのだが、

逆説的であるが、pythonの魅力はプライベート変数が無いなど内部の状態が見える事。
selfを明示的に記述する事にもメリットがあるって事かな。

super - 親クラスのメソッドを呼び出す

自分自身のインスタンスを示すselfというキーワードが出てきたので、その親クラスのメソッドにアクセスするための組込み関数superについても触れておく。

python3では

ところで、super(self.__class__, self) じゃ、 2回クラスを継承するとダメみたい。

多重継承のクラスの場合、親クラスが複数存在する事になる。
その場合、superはどの親クラスのメソッドを呼び出すのだろう。

superは多重継承にも役立つみたいだ。

  • 9.5.1. 多重継承 - 9. クラス — Python 2.7ja1 documentation

    新スタイルクラス(new-style class) では、協調的な super() の呼び出しのためにメソッドの解決順序は動的に変更されます。このアプローチは他の多重継承のある言語で call-next-method として知られており、単一継承しかない言語の super 呼び出しよりも強力です。

    新スタイルクラスについて、多重継承の全ての場合に 1 つかそれ以上のダイヤモンド継承 (少なくとも 1 つの祖先クラスに対し最も下のクラスから到達する経路が複数ある状態) があるので、動的順序付けが必要です。例えば、全ての新形式のクラスは object を継承しているので、どの多重継承でも object へ到達するための道は複数存在します。基底クラスが複数回アクセスされないようにするために、動的アルゴリズムで検索順序を直列化し、各クラスで指定されている祖先クラスどうしの左から右への順序は崩さず、各祖先クラスを一度だけ呼び出し、かつ一様になる (つまり祖先クラスの順序に影響を与えずにサブクラス化できる) ようにします。まとめると、これらの特徴のおかげで信頼性と拡張性のある多重継承したクラスを設計することができるのです。さらに詳細を知りたければ、 http://www.python.org/download/releases/2.3/mro/ を見てください。

superと多重継承の問題については「継承と多重継承とミックスイン」も参考に。

他のサイトへの記事ばかりではなんなので蛇足ながら簡単なサンプルコードを

class SuperCls(object):
    def method(self, arg):
        print u"スーパクラスのメソッドを実行, arg=",arg

class SubCls(SuperCls):
    def method(self):
        print u"サブクラスのメソッドを実行"
        super(SubCls,self).method("arg")

sub_inst=SubCls()
sub_inst.method()

実行結果

サブクラスのメソッドを実行
スーパクラスのメソッドを実行, arg= arg

python3の場合は組込み関数superの引数を省略しても良い。

class SuperCls(object):
    def method(self, arg):
        print("スーパクラスのメソッドを実行, arg=",arg)

class SubCls(SuperCls):
    def method(self):
        print ("サブクラスのメソッドを実行")
        super().method("arg")

sub_inst=SubCls()
sub_inst.method()

いっけん、組込み関数superの第1引数にクラスオブジェクトを指定する代わりにself.__class__を指定した方がコードとしては美しくて良いように感じる。
しかし、これは2回以上クラスを継承した場合に正しく動作しない。

superの第1引数にself.__class__を指定してみる。

class SuperCls(object):
    def method(self):
        print u"スーパクラスのメソッドを実行"

class SubCls(SuperCls):
    def method(self):
        print u"サブクラスのメソッドを実行"
        super(self.__class__,self).method()

sub_inst=SubCls()
sub_inst.method()

実行結果

サブクラスのメソッドを実行
スーパクラスのメソッドを実行

正しく動作しているのが確認できる。

では更にクラスを継承して

class SuperCls(object):
    def method(self):
        print u"スーパクラスのメソッドを実行"

class SubCls(SuperCls):
    def method(self):
        print u"サブクラスのメソッドを実行"
        # ここで無限ループが発生
        # 「self.__class__」は「SubSubCls」を指しているため再びSubClsのmethodが呼び出されてしまう。
        super(self.__class__,self).method()

class SubSubCls(SubCls):
    def method(self):
        print u"サブサブクラスのメソッドを実行"
        super(self.__class__,self).method()

sub_sub_inst=SubSubCls()
sub_sub_inst.method()

これは無限ループになってしまう。
なぜなら、組込み関数superによってサブクラスからスーパクラスのメソッドを呼び出した場合に渡されるselfはスーパクラスのインスタンスでは無くサブクラスのインスタンスであるため、self.__class__はスーパクラスのクラスオブジェクトでは無くサブクラスのクラスオブジェクトになってしまう。
このため、スーパクラスのsuper関数によって呼び出されるメソッドは更にそのスーパクラスのメソッドでは無く、スーパクラス自身のメソッドになってしまう。
これにより、スーパクラスのメソッドから繰り返し同じ自分自身のメソッドが呼ばれてしまう事になってしまう。

以下のようにクラス名を直接指定してやればこのような事は起こらない。

class SuperCls(object):
    def method(self):
        print u"スーパクラスのメソッドを実行"

class SubCls(SuperCls):
    def method(self):
        print u"サブクラスのメソッドを実行"
        super(SubCls,self).method()

class SubSubCls(SubCls):
    def method(self):
        print u"サブサブクラスのメソッドを実行"
        super(SubSubCls,self).method()

sub_sub_inst=SubSubCls()
sub_sub_inst.method()

実行結果

サブサブクラスのメソッドを実行
サブクラスのメソッドを実行
スーパクラスのメソッドを実行

クラスからアクセスするメソッド

クラスからからアクセスするメソッドには、クラスメソッドスタティックメソッドという2つのメソッドが存在する。
Java等の他の言語ではクラスからアクセスするメソッドは1つしかなく、Javaのクラスメソッドはpythonのスタティックメソッドに相等するよう。

- 静的メソッド (static method) オブジェクト , クラスメソッドオブジェクト - データモデル — Python 2.7ja1 documentation

クラスメソッド

クラスメソッドはデコレータ@classmethodを使って記述する。

インスタンスメソッドと同様、第1引数には必ず自分自身のクラスを示す引数(慣例としてclsという引数名を使う)を明示的に指定する必要がある。
この引数clsを使ってクラスの属性にアクセスする事ができる。

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

MyCls.c_method()

実行結果

MyCls

スタティックメソッド

pythonにはクラスからアクセスするメソッドとしてもう1つスタティックメソッドが存在する。
スタティックメソッドはデコレータ@staticmethodを使って記述する。

クラスメソッドはclsという引数を使って自分自身のクラスにアクセスできるが、スタティックメソッドの場合には自分自身のクラスを示す引数が渡されないので 自分自身のクラスにアクセスできない。

とは言え、クラス名を直接,指定する事で静的にメソッドが定義されているクラスにアクセスする事は可能。
以下に、その例を示す。

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

MyCls.s_method()

実行結果

MyCls


これは、Javaのクラスメソッドと同じ方法。
Python、Ruby、Javaのクラスメソッドとスタティックメソッドの比較されている以下の記事を参考。

クラスメソッドとスタティックメソッドの違い

クラスメソッドとスタティックメソッドの違いについてはいろいろと論じれれている。

要はクラスを継承したときに違いが出てくるという事か。

以下のような基底クラスを定義したとして

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

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

この時

BaseCls.c_method()

BaseCls.s_method()


同じ結果を返す。
とつまり、これだけをみると機能はかわらないので引数clsを省略できるだけスタティィックメソッドの方がいいという事になってしまう。

実行結果

BaseCls

このクラスを継承した以下のようなクラスを定義したとする。

class SubCls(BaseCls):
    pass

この時

SubCls.c_method()

の実行結果は

SubCls

と動的に自分自身のクラス名を返すのに対して、

SubCls.s_method()

の実行結果はBaseClsのクラス名と直接結びついているため

BaseCls

と結果が異なってくる。

サブクラスのスタティックメソッドでもクラスメソッドと同じ動作をさせるためには、以下のようにサブクラスでスタティックメソッドをオーバライトする必要がある。

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

SubCls.s_method()

実行結果

SubCls


このように、スタティックメソッドの場合はcls変数を省略できるというメリットがある反面、クラスメソッドのようにそれを継承したクラスにおいて動的に自分自身を定義しているクラスを取得できないのでクラスメソッドの方が優れているという事になる。

スタティックメソッドは、静的にメソッドが定義されているクラスにアクセスするのでスタティックメソッドと言うのかな?

という事は、クラスメソッドダイナミック(動的)クラスメソッドスタティックメソッドスタティック(静的)クラスメソッドと呼んだ方がいいような。

クラスメソッドとスタティックメソッドとsuper

通常、superは親クラスのインスタンスメソッドを呼び出す時に使用する。
では、superを使ってクラスメソッドやスタティックメソッドを呼び出す事はできるのだろうか。

組込み関数superのリファレンスには、

  • super - 2. 組み込み関数 — Python 2.7ja1 documentation

    第 2 引数が省かれたなら、返されるスーパーオブジェクトは束縛されません。第 2 引数がオブジェクトであれば、 isinstance(obj, type) は真でなければなりません。第 2 引数が型であれば、 issubclass(type2, type) は真でなければなりません (これはクラスメソッドに役に立つでしょう)。

とある。
この文章からすると、クラスメソッドの場合は第2引数にクラスメソッドの第1引数clsを指定すればよいように解釈できる。
また、スタティックメソッドは第2引数を省略すれば良いような気がする。

実際のコードで試してみよう。

まず、スパークラスを定義する。

class SuperMyCls(object):
    c_var = u"スーパクラス変数"

    # クラスメソッド
    @classmethod
    def c_method(cls):
        print u"サブクラスメソッドを実行しました"
        print u"c_var=",cls.c_var

    # スタティックメソッド
    @staticmethod
    def s_method():
        print u"スタティックメソッドを実行しました"

次にサブクラスを定義し、クラスメソッドとスタティックメソッドより、それぞれのsuperクラスのメソッドを呼び出してみる。

class SubMyCls(SuperMyCls):
    c_var = u"サブクラス変数"

    # クラスメソッド
    @classmethod
    def c_method(cls):
        print u"スパークラスメソッドを実行しました"
        super(SubMyCls, cls).c_method()

    # スタティックメソッド
    @staticmethod
    def s_method():
        print u"スタティックメソッドを実行しました"
        super(SubMyCls).s_method()
        SuperMyCls.s_method()

サブクラスのクラスメソッドを実行してみる。

SubMyCls.c_method()

実行結果

スパークラスメソッドを実行しました
サブクラスメソッドを実行しました
c_var= サブクラス変数

スーパクラスのクラスメソッドが実行されて、スーパクラスのクラスメソッドよりサブクラスのクラス変数にアクセスできているのが確認できる。

では、スタティックメソッドを実行してみる。

SubMyCls.s_method()

実行結果

スタティックメソッドを実行しました
Traceback (most recent call last):
... 途中略
    super(SubMyCls).s_method()
AttributeError: 'super' object has no attribute 's_method'

s_methodが無いと怒られてしまった。

superを使ってスタティックメソッドにアクセスする事はできないのだろうか?
そもそも、スタティックメソッドはクラスオブジェクトclsやインスタンスオブジェクトselfにアクセスできないので、直接、スーパクラスのクラス名を指定しない限りスーパクラスの情報にアクセスできないので無理という事なのか?

インスタンス経由のメソッドとクラス経由のメソッド

ほとんどの場合、インスタンスメソッドはその名のとおりクラスのインスタンスを介してアクセスされる。
これを本稿ではインスタンス経由のメソッドと呼ぶ事にする。

my_inst.i_method # (1)

これに対して、クラスメソッドやスタティックメソッドはクラスを介してアクセスされる。
これをクラス経由のメソッドと呼ぶ事にする。

# クラスメソッドの呼び出し # (2)
MyCls.c_method()
# タティックメソッドの呼び出し
MyCls.s_method() # (3)

しかし、メソッドはクラスからもインスタンスからも呼び出す事が可能である。
インスタンスメソッドをクラスを介して呼び出したり,逆にクラスメソッドやスタティックメソッドをインスタンスを介して呼び出す事ができるという事。

インスタンスメソッドをクラス経由でアクセス

インスタンスメソッドをクラス経由でアクセスするには、
インスタンスメソッドには自分自身のインスタンスを特定できる引数selfが必要なので、
それを指定して、

MyCls.i_method(my_inst) # (4) -> (1)と等価

多分、(1)の呼び出しの時には、pythonの内部ではこういう変換がおこなわれているのだろう。

クラスメソッドとスタティックメソッドをインスタンスを経由でアクセス

逆に、クラスメソッドとスタティックメソッドをインスタンスを経由して呼び出すには、そのまま

my_inst.c_method() # (5) -> (2)と等価
my_inst.s_method() # (6) -> (3)と等価

ここでひとつ疑問が、

(4)のインスタンスメソッドをクラス経由でアクセスした時には自分自身のインスタンスを特定できる引数selfが必要であったが、
(5)の場合にはインスタンスのクラスを示すcls引数は指定していない。

これは、インスタンスの場合にはひとつのクラスから複数のインスタンスを生成できるのに対して、クラスの場合にはそのインスタンスの属するクラスは1つしかない(多重継承の場合は複数のクラスになるが、メソッドを定義しているクラスは1つに特定できる)のでクラスの特定が可能であるという事。
そしてpythonの内部では引数clsにクラスオブジェクトを渡す処理がおこなわれているのだろうという事が推測される。

クラスメソッドだからこういう内部処理をしなさいとかスタティックメソッドだからこういう内部処理をしなさいという操作が@classmethodや@staticmethodのデコレータによっておこなわれているという事なのであろう。

これらのメソッドの内部動作の仕組みについてはメソッドディスクリプタの擬似コードを参照。

ページのトップへ戻る