classmethod と
staticmethod ってなに?



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

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



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







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

#
# 対話モード >>> に
# コピペで実行できます。
#
class Cls:
    def method(self):
        return self
    
    # クラスメソッド
    @classmethod
    def class_method(cls):  # 第一引数は cls と書く
        return cls          # 第一引数 cls には
                            # クラスオブジェクト Cls が
                            # 代入される。
    
    @staticmethod
    def static_method():    # 第一引数はなくても大丈夫
        return None

ins = Cls()

# インスタンスからの呼び出し方
ins.method()         is ins
ins.class_method()   is Cls
ins.static_method()  is None

# クラスからの呼び出し方
Cls.method(ins)      is ins # 第一引数が必要
Cls.class_method()   is Cls
Cls.static_method()  is None
>>> # インスタンス
... ins.method()         is ins
True
>>> ins.class_method()   is Cls
True
>>> ins.static_method()  is None
True
>>> 
>>> # クラス
... Cls.method(ins)      is ins # 第一引数が必要
True
>>> Cls.class_method()   is Cls
True
>>> Cls.static_method()  is None
True
>>> 

# 1. 使い方

# 1.1. classmethod

インスタンス化はしていない状態で、 クラスオブジェクトに対する操作をしたいときに使うもの程度の理解しかできていません。

classmethod は、クラスオブジェクトから直接呼び出すことができるようになります。 普通のメソッドだと第一引数にインスタンスオブジェクトが必要になります。

# クラスからの呼び出し方
Cls.method(ins)      is ins # 普通のメソッドは第一引数が必要
Cls.class_method()   is Cls # クラスメソッドはいらない

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

# 1.2. staticmethod

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

もちろん staticmethod は、たんなる関数なので、 クラス直下ではなくモジュール直下で定義することもできます。

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

class Cls:
    # このクラスでしか使わない関数なんやな...
    @staticmethod
    def static_method():
        return None

# モジュール全体で使う関数なんやな...
def static_method():
    return None

# 2. コードの補足

# 2.1. cls

第一引数にメソッドを呼び出してきたインスタンスオブジェクトではなくクラスオブジェクトを代入します。 クラスメソッドの第一引数には self ではなく 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

# 2.2. デコレータ

ちなみに @デコレータ と呼ばれるものです。 簡単に似たようなものを実装することができます。

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

def staticmethod(method):
    def decorated_method(self, *args, **kwargs):
        # self を省略してメソッドを呼び出す。
        return method(*args, **kwargs)
    return decorated_method

関数の中で定義された関数を クロージャ と呼びます。

ただし上記の実装は、あくまでも似たようなもので、クラスオブジェクトからクラスメソッドを呼び出すことはできません。

# インスタンスからの呼び出し方
ins.class_method()   is Cls

# クラスからの呼び出し方
# Cls.class_method()   is Cls これはできない

このような挙動を実装するには、属性参照された時に起動する ディスクリプタ が必要になります。 最後にご紹介いたしますが、頻繁には登場しないので、知らなくても大丈夫かなと思います。

デコレータとクロージャは、とりあえず言葉だけ頭の片隅にあってもいいかもしれません。 必要になったら適当にググる程度の感覚でいいと思います。

# 3. 2つの疑問

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

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

# 3.1. 他言語の観点から

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

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

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

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

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

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

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

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

# 3.3. 副作用の観点から

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

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

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

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

class C:
    # インスタンスに共通の値
    # クラス変数
    value = None

    # インスタンスに共通の処理
    # メソッド
    def method(self):
        ...

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

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

もしそれが同じクラスのオブジェクトが代入される引数だった場合、 複数の関数をクラスにまとめられたりすることがあります。 上手くいけば、クラスにまとめると多態性を使って if 文のネストを外すことができます。

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

# でも...

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

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

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

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





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

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

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