Python の classmethod と staticmethod ってなに?



クラスメソッドは、インスタンスオブジェクトを操作することができないメソッドです。

スタティックメソッドは、インスタンスオブジェクトもクラスオブジェクトも操作することができないメソッドです。



インスタンス
オブジェクト
クラス
オブジェクト
メソッド操作できる操作できる
クラスメソッド操作できない操作できる
スタティックメソッド操作できない操作できない







1. どうやって使うの?

クラスメソッド、スタティックメソッドを定義するには @classmethod , @staticmethod を書き加えます。@ はデコレータです。デコレータについては後述します。

1.1. classmethod デコレータ

第一引数にメソッドを呼び出してきたインスタンスオブジェクトではなくクラスオブジェクトを代入します。クラスメソッドの第一引数には、いつも cls を使ってください。

class C(object):
    # 第一引数にクラスオブジェクトが代入される。
    @classmethod
    def cls_method(cls):
        print(cls.__name__)
    
    def method(self):
        pass

o = C()

# クラスメソッド
o.cls_method()  # C
C.cls_method()  # C

# 普通のメソッドではできない。
C.method()  # TypeError
>>> o.cls_method()  # C
C
>>> C.cls_method()  # C
C
>>>
>>> C.method()
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: method() missing 1 required positional argument: 'self'
>>> 


クラスメソッドの第一引数は cls を使うように PEP 8 で定められています。

関数とメソッドの仮引数
Function and Method Arguments

インスタンスメソッドの第一引数には、いつも self を使ってください。
Always use self for the first argument to instance methods.

クラスメソッドの第一引数には、いつも cls を使ってください。
Always use cls for the first argument to class methods.

もし関数の仮引数名に予約語と同じものを使いたい場合は、スペルを省略したり変更したりするのではなく、仮引数名の末尾にアンダースコアを1文字追加してください。つまり clss とか klass とか書くよりも class_ と書いた方が良いです。(もしかしたら、予約語を使いたい時は、同義語を使った方が、より良いやり方かもしれません。)
If a function argument's name clashes with a reserved keyword, it is generally better to append a single trailing underscore rather than use an abbreviation or spelling corruption. Thus class_ is better than clss. (Perhaps better is to avoid such clashes by using a synonym.)
PEP 8 - Style Guide for Python Code


実践的な使い方は書籍 Effective Python だと「項目24 @classmethodポリモルフィズムを使ってオブジェクトをジェネリックに構築する」に書かれていました。 が Thread という機能を使っていて、理解できていない... orz

1.2. staticmethod デコレータ

第一引数にメソッドを呼び出してきたインスタンスオブジェクトを代入することを省略します。 staticmethod デコレータは、そのクラスでしか使わない関数を定義する時に使います。

class C(object):
    def method(self):
        self.stc_method()
    
    # クラス C  でしか使われない関数なんやな...
    # 第一引数を省略できる。
    @staticmethod
    def stc_method():
        print('Hello, world!')

o = C()
o.method()  # Hello, world!

2. 意味があるの?

答え: 〜しないと決めると可読性があがるので、いい機能かなと個人的に思っています(あくまで個人の感想です)。

〜することができないようにする機能と言うのは、意味があるのでしょうか? クラスメソッドなら cls=type(self) をすればいいだけですし、スタティックメソッドなら単純に self を使わなければいいじゃんと言うだけの話です。

2.0 いつ使うのか?

答え: 使える時は使うのが望ましいのかなと思ったりします。

Guido は self を書く必要性を説明する時に staticmethod, classmethod が使えるようになるやろって力説してたのと (Python のメソッドと関数の違いってなに?)、 staticmethod, classmethod は、import しなければ使えない標準ライブラリの中ではなく import しなくても使える組み込み型として定義されていているからです。

import しなくても使えると言うことは、よく使うことが想定されていると言うことです。 実際、標準ライブラリのコードの中でよく使われているのを見ているような気がするからです。

2.1. 副作用の観点から

「副作用」という言葉を考えた時に、無いよりは、あった方がいいかなと思いました。 副作用については、こちらで説明させていただきました。
Python で mutable と immutable の違い - 可読性 - 副作用ってなに?

classmethod, staticmethod を明示して影響の範囲、副作用の範囲を限定することは、 インスタンス self だけとは言え、精神的に安心感があります(※ あくまで個人の感想です)。

クラスは「オブジェクトに共通する値と処理をまとめたもの」です。 Pythonスクリプトを書く時に、副作用を受けるオブジェクトを関数の第一引数に寄せて置きます。

もしそれが同じクラスのオブジェクトが代入される引数だった場合、 うまい具合に複数の関数をクラスにまとめられたりすることがあります。

つまり第一引数 self の機能を制限することは、 第二、第三引数を制限するのとは、ごくごくちょっとだけ性格が異なるという訳です。

ただ、第一引数 self だけではなく、第二、第三引数にも副作用を与えることはできます。 もしシステム全体を通して副作用を与えるのは self に限定するという設計なら staticmethod, classmethod も有効かもしれません。

ただ、だからと言って、システム全体を通して副作用を与えるのは self に限定するみたいな設計は聞いたことがありません。無理やり 単一責任の原則 と絡めて、メソッドが副作用を与えるのは第一引数 self に "なるべく" 限定するという方針もなきにしもあらずとは思うのですが、ないので、なんとも苦しい感じです(でもそしたらいい感じに、副作用を制御できる設計ができたりするのかな...)。

さらに悪いことにPython ではユーザ定義クラスの属性は基本 mutable だし、変数を定数にすることさえできません。 そうなってくると self だけを staticmethod, classmethod をわざわざ書いてまで副作用の範囲を限定しても、若干焼け石に水感もあります。

2.2. コードを整理する観点から

もちろん staticmethod は、たんなる関数なので、クラス直下ではなくモジュール直下で定義すれば それで副作用の範囲を明示することができます。 しかし、モジュール直下で関数を定義してしまうと "モジュール内で定義されたクラスや関数から呼び出される汎用的な関数なんやな" と読む側に誤解を与えてしまいます。

# モジュール全体で使う関数なんやな...
def stc_method():
    print('Hello, world!')


名前空間がほしいだけのクラスも不要です。」という指摘もあるのですが、 個人的にはコードを整理整頓しておきたいので、使わせて欲しいかなと思ったり、思わなかったり...

また、名前空間がほしいだけのクラスも不要です。 そもそもvalidateにもっと厳密な名前をつけるか、 import 時にエイリアスを付ける、といった解決策はいくらでもあります。 参照スコープが限定されているモジュールシステムの中ではあまり厳密ではありません。

オブジェクト指向の呪いと、その避け方
ttps://mizchi.hatenablog.com/entry/2018/07/31/124354

2.3. 他言語の観点から

実際 Swift, Kotlin などの最近生まれた言語では、たしかなかった気がします。 Ruby には staticmethod はないですが classmethod に該当するものはあるそうです。 きっと実装が面倒だからだと思います。利益が無いのに実装が面倒なものを作っても、費用対効果薄いですからね。

3. どんな風に実装されているの?

ちなみに、Python ではスタティックメソッドやクラスメソッドが簡単に実装できます。 なぜ簡単に実装できるかというと、実はメソッド定義時に self を書いているからです。

3.1. 簡単には...

classmethod も staticmethod も組み込み関数として最初から付いてきているので自分で実装することはありませんが、 このような classmethod, staticmethod は、クロージャを使えば簡単に実装することができます。クロージャについては後述します。

def classmethod(method):
    def decorated_method(self, *args, **kwargs):
        return method(type(self), *args, **kwargs)
    return decorated_method
def staticmethod(method):
    def decorated_method(self, *args, **kwargs):
        return method(*args, **kwargs)
    return decorated_method

3.2. 正確には...

公式マニュアルに書かれた疑似コードによるとスタティックメソッドも、クラスメソッドも、 ディスクリプタ を使って実装されているらしいです。

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc
class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f

「Python のメソッドと関数の違いってなに?」 の中で簡単に、ディスクリプタがどういった動作をしているのかだけ、解説させていただきました。

ただ、ディスクリプタの具体的な使い方については Effecvtive Python を買って 4 章を読んで見てください。たぶん、これが一番わかりやすいです。

4. デコレータとクロージャって何?

デコレータは、複数の関数、メソッドに共通した前処理と後処理を追加したいときに使います。クロージャ(関数閉包)とは、閉じて包まれた名前空間を持った関数です。