Python の classmethod と staticmethod ってなに?



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

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



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







1. どうやって使うの?

クラスメソッド、スタティックメソッドを定義するには classmethod , staticmethod を使います。

1.1. classmethod

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

class C(object):
    # 第一引数にクラスオブジェクトが代入される。
    @classmethod
    def cls_method(cls):
        print(cls.__name__)
    
    def call_cls_method(self):
        # 1) メソッド内での呼び出し
        self.cls_method()

    def method(self):
        ...

o = C()
o.call_cls_method()

# 2) メソッド外での呼び出し
C.cls_method()  # C

#    普通のメソッドではできない。
#    なぜなら関数として呼び出されているから
C.method()  # TypeError
>>> o.call_cls_method()
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'
>>> 

1.1.1. デコレータ

ちなみに @ はデコレータと呼ばれるものです。 デコレータについては別に知らなくても使えるのでご安心ください。

とりあえず、こういう書き方をするんだと覚えておけばいいかなと思います。 気になるようでしたら Google で検索してみてください。

デコレータそのものは難解なものではないのですが、 class を使ったクラス定義文の中で使う @ デコレータなので、 逆に調べてようとすると、すこし沼にハマるかなと、 いくらか感じるとこがあります。

もしclassmethod, staticmethod に絡めてデコレータについて調べる場合は、 メソッドは呼び出されると関数に変換されているということを、 押さえておくことを個人的にオススメいたします。

1.1.2. cls

クラスメソッドの第一引数は 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. (Perhaps better is to avoid such clashes by using a synonym.)
PEP 8 - Style Guide for Python Code

1.1.3. 実践的な使い方

実践的な使い方は書籍 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. 2つの疑問

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

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

2.1. 他言語の観点から

Ruby や Kotlin には staticmethod がありません。 なきゃいけないものか?と聞かれると、別になくてもいいのかなと思ったりもします。 以下の記事は、ちゃんと読めてません...。

2.2. Python での立ち位置の観点から

Python では classmethod と staticmethod は、 どのような立ち位置、評価のされ方をしているのでしょうか? 結論から言えば、良いものとして捉えられている気配があります。

まず、第一に staticmethod, classmethod は、 import しなければ使えない「標準ライブラリ」の中ではなく import しなくても使える「組み込み型」として定義されていているからです。

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

このようなものの温度感の例として Python 2 から 3 にかけて reduce は、Guido から不要と嫌われて 組み込み関数から除外、標準ライブラリ functools の中に押し込まれました。

また、第二に Guido は self を書く必要性を説明する時に staticmethod, classmethod を簡単に実装できるということを例として挙げていました。

これらのことを踏まえると、 別に重要だ、というわけでは無いのですが、 無用なものというわけでもなさそうなことは、感じます。

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

もちろん staticmethod は、たんなる関数なので、クラス直下ではなくモジュール直下で定義すれば それで副作用の範囲を明示することができます。

しかし、モジュール直下で関数を定義してしまうと "モジュール内で定義されたクラスや関数から呼び出される汎用的な関数なんやな" と読む側に誤解を与えてしまいます。

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

2.4. 副作用の観点から

〜することができないようにする機能と言うのは、意味があるのでしょうか? クラスメソッドなら cls=type(self) をすればいいだけですし、 スタティックメソッドなら単純に self を使わなければいいじゃんと言うだけの話です。 「副作用」という言葉を考えた時に、 無いよりは、あった方がいいかなと思いました。 副作用については、こちらで説明させていただきました。

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

ユーザ定義クラスってなに?

class を使って定義したクラスとはなんでしょうか?

クラスは「オブジェクトに共通する値と処理をまとめたもの」と自分は考えています。 またクラスは 「self に対する処理をまとめたもの」と考えることもできるかなと思っています。

例えば Python のスクリプトを書く時に、 副作用を受けるオブジェクトを関数の第一引数に寄せて置きます。

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

そう考えてくると第一引数 self の機能を制限することは、 第二、第三引数を制限するのとは、ごくごくちょっとだけ性格が異なる感じがします。

でも...

しかしながら、第一引数 self だけではなく、第二、第三引数にも副作用を与えることはできます。

無理やり 単一責任の原則 と絡めて、メソッドが副作用を与えるのは第一引数 self に "なるべく" 限定するという方針もなきにしもあらずとは思うのですが。

さらに悪いことにPython ではユーザ定義クラスの属性は基本 mutable だし、 定数を作ることさえできません。

そうなってくると self だけを staticmethod, classmethod と書いてまで副作用の範囲を限定しても、もはや焼け石に水感もあります。





3. どうやって実装されているの?

staticmethod と classmethod は「ディスクリプタ」を使って実装されています。 実は、メソッドを呼び出した時に関数に変換されているのですが、 この時にも「ディスクリプタ」が使われています。

以下の記事では、 まずメソッドから関数への変換にディスクリプタが使われていることを紹介しつつ、 練習問題として staticmethod と classmethod の実装につなげています。