YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

staticmethodとclassmethodの違いでC++的な静的メソッドを考える

動機

C++のようなstaticメソッドを作ろうとしてはまったのでメモ。

関数オブジェクトとメソッドオブジェクト

まず最初に関数オブジェクトとメソッドオブジェクトがあることに気をつける。その両者については2番目のリンクがわかりやすい。要約すると

  • 関数オブジェクトはクラスオブジェクトに代入されるとメソッドオブジェクトになる
  • メソッドオブジェクトにもunboundメソッドとboundメソッドがある
    • 関数オブジェクト+クラスオブジェクト = メソッドオブジェクト(unbound)
    • 関数オブジェクト+クラスオブジェクト+インスタンス = メソッドオブジェクト(bound)

クラスメソッドと静的メソッド

メソッドオブジェクト(unbound)を外から見たい

上記をざっくり考えると、関数オブジェクトとメソッドオブジェクト(bound)は実体があるので代入もできると。
じゃあメソッドオブジェクトを他のクラスオブジェクトに定義する場合は?と思ったときにクラスメソッドと静的メソッドが出てきます。

デコレータを使う

デコレータ@classmethodと@staticmethodを使うとそれぞれクラスメソッドと静的メソッドを作成することができます。クラスメソッドと静的メソッドの違いは3番目のリンクで例を用いてふれています。
今回は静的メソッドを使ってクラスの宣言でうまいこと利用するのが目的なので、上記リンクを参考にサンプルコードを書きます。

# ベースクラス
class hoge:
    @staticmethod
    def foo(x):
        print x

    @classmethod
    def bar(cls, x):
        print cls.__name__, x

# hogeとは関係のないクラス
class piyo:
    pass

# hogeの派生クラス
class fuga(hoge):
    pass

# インスタンス生成前のfoo()とbar()
print hoge.foo # <function foo at ADDRESS1>
print hoge.bar # <bound method classobj.bar of <class __main__.hoge at ADDRESS2>>

# 各クラスのインスタンス作成
h = hoge()
p = piyo()
f = fuga()
# piyoの「インスタンス」に各メソッドオブジェクトを渡す
p.foo = h.foo
p.bar = h.bar

# タイプを表示
print h.foo # <function foo at ADDRESS1>
print h.bar # <bound method classobj.bar of <class __main__.hoge at ADDRESS2>>
print p.foo # <function foo at ADDRESS1>
print p.bar # <bound method clsssobj.bar of <class __main__.hoge at ADDRESS2>>
print f.foo # <function foo at ADDRESS1>
print f.bar # <bound method classobj.bar of <class __main__.fuga at ADDRESS3>>

# 結果
h.foo('buz') # buz
h.bar('buz') # hoge buz
p.foo('buz') # buz
p.bar('buz') # hoge buz
f.foo('buz') # buz
f.bar('buz') # fuga buz

foo()とbar()のタイプの違いを見ればわかるのですが、静的メソッドの場合、扱いは関数オブジェクトと同等となります。それに対してクラスメソッドの場合は扱いはメソッドオブジェクト(bound)となります。個人的に面白いなと思ったのはクラスメソッドの場合はインスタンスを生成しないでもメソッドオブジェクト(bound)になっているところです。
ここで注目したいのがpiyoのオブジェクトとfugaのオブジェクトでの挙動の違いです。piyoはhogeとは全く関係のないクラスで、C++でありがちないわゆる静的メソッドの呼び出しをしています。この状態ではクラスメソッドと静的メソッドの差がでていませんが、なぜクラスメソッドがわざわざ第1引数にクラスオブジェクトをとるかと言えば、それが継承されたときに差が出るからだとわかります。
fugaでの結果を見ると、foo()の場合は何も変わりませんが、bar()の型がfugaクラス内でのアドレスに変わっていることが見て取れます。さらに結果を見るとクラス名を表示させる部分でちゃんと結果が反映されています。
これによりクラスメソッドは継承したクラスの中のプロパティを反映した動作をさせられると言う点で優れています。