2008年9月16日火曜日

Python のクラスメソッド – デコレータ @classmethod, @staticmethod を使って

1. メソッドをクラスメソッドにするときは、デコレータを使う

あるメソッドをクラスメソッドにするとき、 Python ではデコレータを使うことができる。

2.1 組み込み関数 によると、(太字は引用者による)

classmethod( function)

function のクラスメソッドを返します。

クラスメソッドは、インスタンスメソッドが暗黙の第一引数としてインスタンスをとるように、第一引数としてクラスをとります。クラスメソッドを宣言するには、以下の書きならわしを使います:

class C:
    @classmethod
    def f(cls, arg1, arg2, ...): ...

ふーむ、ここでも第一引数が必須で、それがクラスを参照するということか。Python のこういう書き方は、最初とっつきにくいと感じた。 (+_+)

デコレータの書き方としては、関数の前に

@classmethod

を置けばいいだけ。

 

2. クラスメソッドを試してみる

デコレータを使う場合
class Hoge:
    @classmethod
    def sayHoge(cls, x):
        print x + " hoge"

Hoge.sayHoge("ABC")
Hoge().sayHoge("abc")

結果は、

ABC hoge
abc hoge

上記の通り、クラス経由、インスタンス経由で呼出すことができる。

 

デコレータを使わない場合

デコレータの記法が使える前は、次のように組み込み関数

classmethod()

を使っていた。

class OldHoge():
    def sayHoge(cls, x):
        print x + " hoge"
        
    sayHoge = classmethod(sayHoge)
    
OldHoge.sayHoge("DEF")
OldHoge().sayHoge("def")

つまり、classmethod() が、普通のメソッドをクラスメッドになるようにしている。

この使い方を見ると、デコレータは、突然導入された特殊な記法ではなく、関数をラップしているだけだと実感できる。

 

3. staticmethod() もある

ところで、一つ気になることがある。

クラスメソッドは C++ や Java における静的メソッドとは異なります。そのような機能を求めているなら、staticmethod() を参照してください。 バージョン 2.2 で 新たに追加 された仕様です。 バージョン 2.4 で 変更 された仕様: 関数デコレータ構文を追加しました

(同上の classmethod() の説明より、太字は引用者による)

使い方を見てみると、先ほどの classmethod() とは大きく違う点がある。

2.1 組み込み関数  によると、

staticmethod( function)

function の静的メソッドを返します。

静的メソッドは暗黙の第一引数を受け取りません。静的メソッドの宣言は、以下のように書き慣わされます:

class C:
    @staticmethod
    def f(arg1, arg2, ...): ...

staticmethod() には、classmethod() にあった 第 1 引数は必要ない

Python における静的メソッドは Java や C++ における静的メソッドと類似しています。より進んだ概念については、 classmethod() を参照してください。 バージョン 2.2 で 新たに追加 された仕様です。 バージョン 2.4 で 変更 された仕様: 関数デコレータ構文を追加しました(同上より)

Java の static メソッドのつもりで書いたので、混乱してきた。

ここまでを整理してみる。

  • classmethod() に似た staticmethod() という関数がある。
  • 両方ともバージョン 2.2 で追加され、2.4 でデコレータ構文を使うことができるようになった。
  • classmethod() の方が staticmethod() より進んだ概念らしい。

問題は最後の項目。何がどう進んだ概念だろうか?(@_@)

 

classmethod() との違い

3.2 標準型の階層 によると、

静的メソッド (static method) オブジェクト
静的メソッドは、上で説明したような関数オブジェクトからメソッドオブジェクトへの変換を阻止するための方法を提供します。(…)
 
クラスメソッドオブジェクト
クラスメソッドオブジェクトは、静的メソッドオブジェクトに似て、別のオブジェクトを包むラッパであり、そのオブジェクトをクラスやクラスインスタンスから取り出す方法を代替します。このようにして取得したクラスメソッドオブジェクトの動作については、上の ``ユーザ定義メソッド (user-defined method)'' で説明されています。(…)

うーん、よくわからない ^^;

newbie-question: difference between classmethod and staticmethod in Python2.2 によると、

Use a staticmethod when you know which class you want to access as you are writing the code. (…)
Use a classmethod if you have a class hierarchy and want the method to operate on the actual class used in the call rather than the class where it was defined:

つまり、staticmethod は、アクセスするためのクラスがわかっている必要がある。それに対して、 classmethod は違う。多分、この違いについて「より進んだ概念」と称しているのかな?

具体的な違いについては、コードを見た方が理解が早い。上記の引用先には、コードの例が書かれている。自分でも試しておこう。

予め断わっておくと、先ほど書いたコードは、classmethod のメリットを全く享受していない。理由は、@classmethod にしたメソッドの第 1 引数である、当該クラスへの参照を全く利用していないため。

 

4. クラスメソッドを試してみる

次のように、@classmethod を使ったクラスと、@staticmethod を使ったクラスを作り、それぞれサブクラスを作成する。

080916-003

# ----------------------------------------------------------
# @classmethod を使った場合

class Hoge:
    @classmethod
    def setHoge(cls, x):
        # クラス名が固定されていない
        cls.HOGE = x

# クラス変数を設定
Hoge.setHoge("100"); print '*** Hoge.setHoge("100")'
print "Hoge.HOGE: " + Hoge.HOGE

class SubHoge(Hoge):
    pass

print "SubHoge.HOGE: " + SubHoge.HOGE

# クラス変数をサブクラスから設定
SubHoge.setHoge("200"); print '*** SubHoge.setHoge("200")'

print "Hoge.HOGE: " + Hoge.HOGE         # サブクラスでの変更が影響しない
print "SubHoge.HOGE: " + SubHoge.HOGE

# ----------------------------------------------------------
# @staticmethod を使った場合
print "-"*30

class Piyo():
    @staticmethod
    def setPiyo(x):
        # クラス名 Piyo が固定されている
        Piyo.PIYO = x

# クラス変数を設定
Piyo.setPiyo("100"); print '*** Piyo.setPiyo("100")'
print "Piyo.PIYO: " + Piyo.PIYO

class SubPiyo(Piyo):
    pass

print "SubPiyo.PIYO: " + SubPiyo.PIYO

# クラス変数をサブクラスから変更
SubPiyo.setPiyo("200"); print '*** SubPiyo.setPiyo("200")'

print "Piyo.PIYO: " + Piyo.PIYO           # サブクラスでの変更が影響
print "SubPiyo.PIYO: " + SubPiyo.PIYO

結果は、

*** Hoge.setHoge("100")
Hoge.HOGE: 100
SubHoge.HOGE: 100
*** SubHoge.setHoge("200")
Hoge.HOGE: 100
SubHoge.HOGE: 200
------------------------------
*** Piyo.setPiyo("100")
Piyo.PIYO: 100
SubPiyo.PIYO: 100
*** SubPiyo.setPiyo("200")
Piyo.PIYO: 200
SubPiyo.PIYO: 200

上記の結果を見てわかるように、classmethod を使った方では、cls. で修飾してクラス変数にアクセスしているため、サブクラスでの変更が影響していない。(ということだと思う ^^;)

 

5. Java の static メソッドとの比較

Java での動作も確認しておく。

public class Piyo {
    public static String PIYO = "";
    
    public static void setPiyo(String x){
        Piyo.PIYO = x;
    }

    public static void main(String[] args) {
        // クラス変数を設定する
        Piyo.setPiyo("100");  System.out.println("Piyo.setPiyo(\"100\");");
        System.out.println("Piyo.Piyo: " + Piyo.PIYO);
        
        System.out.println("SubPiyo.PIYO: " + SubPiyo.PIYO);
        
        // クラス変数をサブクラスから設定する
        SubPiyo.setPiyo("200"); System.out.println("SubPiyo.setPiyo(\"200\");");
        
        System.out.println("Piyo.PIYO: " + Piyo.PIYO);          // サブクラスでの設定が影響している
        System.out.println("SubPiyo.PIYO: " + SubPiyo.PIYO);
    }
}

class SubPiyo extends Piyo{}

結果は、

Piyo.setPiyo("100");
Piyo.Piyo: 100
SubPiyo.PIYO: 100
SubPiyo.setPiyo("200");
Piyo.PIYO: 200
SubPiyo.PIYO: 200

Python の @staticmethod と同じ動作であることがわかる。