classmethodによるクラスメソッド

入門Python3 6.10


classmethodデコレータを使うと、クラスメソッドを作ることができます。

class Foo():
    def hello():
        return "hello foo"
    
    @classmethod
    def hello_print (cls):
        print(cls.hello())

Foo.hello_print()
$ py main.py
hello foo

clsにはFooクラスが入ります。つまりこの処理は以下の処理と等価です。

class Foo():
    def hello():
        return "hello foo"
    
    # classmethodデコレータを使わない
    def hello_print (cls):
        print(cls.hello())

Foo.hello_print(Foo) # 自分でFooクラスを渡す
$ py main.py
hello foo

ぶっちゃけて言えばFooを二回書くのが面倒なんで@classmethodでやろうよってことなのかな。

とういか一体なんの役に立つのか、そもそもclsなんて引数を受け取る必要あるの?クラス名書けばいいじゃん、以下のコードじゃダメなの?

class Foo():
    def hello():
        return "hello foo"
    
    def hello_print (): # clsなんて受け取らない
        print(Foo.hello()) # Foo.hello()で取ればいいじゃん

Foo.hello_print()
$ py main.py
hello foo

clsなんて受け取らずにFoo.hello()でアクセスして操作すればいいじゃない、結果も同じだしさ。

・・・とまぁ一見確かにclassmethod使おうがどっちでも同じに見えますが、継承した時にまったく挙動が変わります。


このclassmethodを使ってないFooクラスを継承した場合どうなるか見てみましょう

class Foo():
    def hello():
        return "hello foo"
    
    def hello_print ():
        print(Foo.hello())

class Bar(Foo):
    def hello():
        return "hello bar"

Foo.hello_print()
Bar.hello_print()
$ py main.py
hello foo
hello foo

Barでhelloをオーバーライドし別の文字列を返すようにしたのに"hello foo"になってしまっています。

それも当然でFoo.hello_print内でFoo.helloを呼び出してるのだから当たり前の結果です。

でもこれは(このコードを書いた人にとっては)意図した動きではないはずです。


これをクラスメソッドに変えた場合、意図通りに動くことになります。

class Foo():
    def hello():
        return "hello foo"
    
    @classmethod
    def hello_print (cls):
        print(cls.hello())

class Bar(Foo):
    def hello():
        return "hello bar"

Foo.hello_print()
Bar.hello_print()
$ py main.py
hello foo
hello bar

こうすることでclsには呼ばれたクラスのクラスオブジェクトが入ってくるので、clsの中身はFooで呼べばFooクラスだしBarで呼べばBarクラスになるとういことです。

基本的にはこの継承可能なクラスメソッドを定義できるという点で便利なので使っていきたいのですが、逆に言えば継承されたときに動作が変わって欲しくない場合はむしろclassmethodを使わないという判断も有りかとは思います(いやその用途ならアンダーバー2個から始まるメソッドにすればいいんじゃないの・・・)


ちなみにclsという引数名はselfと同様にPythonの慣習的に自身のクラスオブジェクトが来る場合にそう付けるということみたいです、従いましょう。