Last Updated: 2/6/2024, 5:44:57 AM

classmethod と
staticmethod ってなに?

# はじめに

# ◯ いつ使うの?

 クラスメソッド  は、 主にインスタンス化する方法を、__init__ の他にも定義したい時に使います。

 スタティックメソッド  は、 主にそのクラスで定義されたメソッドの中でしか使わない関数を定義したい時に使います。

# ◯ どう定義するの?どう動くの?

定義は、関数定義文の1行上に @classmethod, @staticmethod と書きます。 動作は、以下のコードをコピペして動かして見てください。

#
# 対話モード >>> に
# コピペで実行できます。
#

#
# Step 1. 定義
#
class クラス:
    def メソッド(self):
        return self
    
    # 第一引数は self ではなく cls と書きます。
    # 第一引数 cls には、クラスが代入されます。
    @classmethod
    def クラスメソッド(cls):  
        return cls            
    
    # 第一引数 self は書きません。
    @staticmethod
    def スタティックメソッド():
        return None


#
# Step 2. 動作
#
インスタンス = クラス()

# インスタンスからの呼び出し方
インスタンス.メソッド() is インスタンス
インスタンス.クラスメソッド() is クラス
インスタンス.スタティックメソッド() is None

# クラスからの呼び出し方
# クラス.メソッド() is インスタンス  # <--- 使えない
クラス.クラスメソッド() is クラス
クラス.スタティックメソッド() is None
>>> # インスタンスからの呼び出し方
>>> インスタンス.メソッド() is インスタンス
True
>>> インスタンス.クラスメソッド() is クラス
True
>>> インスタンス.スタティックメソッド() is None
True
>>> 
>>> # クラスからの呼び出し方
>>> # クラス.メソッド() is インスタンス  # <--- 使えない
>>> クラス.クラスメソッド() is クラス
True
>>> クラス.スタティックメソッド() is None
True
>>> 

インスタンスからの呼び出しとクラスからの呼び出しで操作できるできない、を表にすると以下のようになります。


インスタンスからの呼び出し
インスタンス クラス
メソッド 操作できる 操作できる
クラスメソッド 操作できない 操作できる
スタティックメソッド 操作できない 操作できない

クラスからの呼び出し
インスタンス クラス
メソッド - 使えない
クラスメソッド - 操作できる
スタティックメソッド - 操作できない

# ◯ 補足

# 1. クラスオブジェクトとインスタンスオブジェクト

インスタンス、正確にはインスタンスオブジェクト、クラス、正確にはクラスオブジェクトについては、 以下の記事で確認してきました。 インスタンスオブジェクトとクラスオブジェクトの詳細は知らなくても、先に読み進められるかなと思っています。

# 2. メソッドと関数の違い

なぜクラスからメソッドが使えないのでしょうか? それは TypeError になるからです。

class クラス:
    def メソッド(self):
        return self

クラス.メソッド() is インスタンス
# TypeError

クラス.メソッド(インスタンス) is インスタンス
# これは動きます。
>>> クラス.メソッド() is インスタンス
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: メソッド() missing 1 required positional argument: 'self'
>>>
>>> クラス.メソッド(インスタンス) is インスタンス
True
>>> 

この動作については、以下の記事で確認してきました。 しかし、いま知らなくても先に読み進められますし、 将来知らないと困ったりハマったりする機能でもありません。

# ◯ ここからは

まず1章では、スタティクメソッドの使い道を見て行きます。 次に2章では、クラスメソッドの使い道を見て行きます。 3章では、機能を制限することに意味があるのかを考えて行きます。 4章では、どうやって実装されているかを見て行きます。

Hello, world!

# 1. スタティックメソッド

インスタンスから呼び出すスタティックメソッドについて考えます。 staticmethod デコレータは、そのクラスでしか使わない関数を定義する時に使います。

通常メソッドを定義する時は、第一引数に self と書きます。 スタティックメソッドでは、この第一引数は書きません。

class クラス:
    # self は要らない
    @staticmethod
    def スタティックメソッド():
        return None

もちろん staticmethod は、たんなる関数なので、 クラス直下ではなくモジュール直下で、関数として定義すればいいんじゃないの? という疑問があります。

def 関数():
    return None

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

class クラス:
    # このクラスでしか使わない関数なんやな...
    @staticmethod
    def スタティックメソッド():
        return None

# モジュール全体で使う関数なんやな...
def スタティックメソッド():
    return None

# 2. クラスメソッド

クラスから呼び出すクラスメソッドについて考えます。 さてクラスメソッドの使いどころは、どこにあるのでしょうか。

#
# 対話モード >>> に
# コピペで動きます。
#
class クラス:
    @classmethod
    def クラスメソッド(cls):
        return cls()

# クラスからオブジェクトを
# インスタンス化させることができます。
クラス()


# クラスメソッドからもオブジェクトを
# インスタンス化させることができます。
クラス.クラスメソッド()

# 2.1. datatime

datatime は、指定された時間を返します。 一方で datatime.now は、現在の時刻を返します。

#
# 対話モード >>> に
# コピペで実行できます。
#
from datetime import datetime
datetime(2019, 1, 1, 0, 0)
datetime.now()
>>> from datetime import datetime
>>> datetime(2019, 1, 1, 0, 0)
datetime.datetime(2019, 1, 1, 0, 0)
>>> datetime.now()
datetime.datetime(2020, 4, 8, 7, 29, 58, 433087)
>>> 

そして datatime.now はクラスメソッドです。 以下のコードは読まなくても大丈夫です。  datatime.now は、 クラスメソッドで定義されているんだな  ということだけ、 押さえておいていただければと思います。

class datetime(date):
    
    ...

    @classmethod
    def now(cls, tz=None):
        "Construct a datetime from time.time() and optional time zone info."
        t = _time.time()
        return cls.fromtimestamp(t, tz)

# 2.2. datatime - こんな実装もできたはず

例えば、引数を与えなければ、現在時刻のオブジェクトを返すという作り方もできたはずです。

#
# 偽物
# こんな作り方もできたはず
#
from datetime import datetime
datetime(2019, 1, 1, 0, 0)
datetime()  # <--- 現在時刻を返す

しかし datatime クラスではそのよな実装にはなっていません。 なぜでしょうか? もしそのような実装をしてしまうと datatime クラスは、 2つのことを行ってしまうからです。 「現在時刻を返す」と「指定された時間を返す」という、 2つのことを行うことになります。

1つのプログラムには1つのことをうまくやらせる。
ガンカーズ: UNIX の哲学 - Wikipedia (opens new window)

なぜ上記の言葉が生まれたかは、以下の本が面白いかなと思います。 一見してお堅そうな本ですが、薄いし、とても面白かったです。

# 2.3. そのほか

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

# 2.3. 第一引数 cls

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

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

クラスメソッドの第一引数には、いつも cls を使ってください。
Always use cls for the first argument to class methods.
PEP 8 - Style Guide for Python Code (opens new window)

# 3. 意味があるの?

クラスからクラスメソッドを呼び出せるという機能が追加されます。 これを使う意味はわかります。 クラスから呼び出すスタティックメソッドの使い道については、一旦脇に置いておきます。


クラスからの呼び出し
インスタンス クラス
メソッド - 使えない
クラスメソッド - 操作できる
スタティックメソッド - 操作できない

一方で、インスタンスからの呼び出しの場合、それ以外は機能を制限しているだけです。 これって意味があるのでしょうか?


インスタンスからの呼び出し
インスタンス クラス
メソッド 操作できる 操作できる
クラスメソッド 操作できない 操作できる
スタティックメソッド 操作できない 操作できない

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

いつ使うの?
答え: 使える時は使うのが望ましいのかなと思ったりします(※ あくまで個人の感想です)。

# 理由 1. 副作用の観点から

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

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

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

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

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

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

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

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

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

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

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

# でも...

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

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

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

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

# 理由 2. KISS の原則から

継承より合成とか依存性逆転の原則といった難しい言葉あるのですが、 要するに全部渡さないで必要なものだけ渡しましょうね、という経験則かなと思っています。

論理的にそれが正しいというわけではないのですが、 そういう世界の中で生きているという感覚かなと思います。 もし全部渡してしまうか、それとも部分的に渡すか、 迷った場合、どっちでもいい場合は、必要なものだけ渡しましょうという感じかなと思っています。

部分的に渡すのは手間です。 依存性を逆転させるためにわざわざ、オブジェクトを分割して渡すか、 あるいは専用のクラスを定義しないといけないです(Java で言えばインターフェイスでしょうか)。 継承ではなく合成をするために、その都度関数を書かないといけません。

そのため辛かったら全部渡せばいいと思います。 例えばウェブアプリケーションフレームワークの Flask は、 積極的にグローバル変数を使って全部渡すスタイルになっています。 その分、Flask はカジュアルに書ける様になっていて人気を博しました。 この辺の塩梅は、とても難しそうです。

# 理由 3. Guido の評価から

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

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

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

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

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

これらのことを踏まえると、 別に重要だ、というわけでは無いのですが、 無用なものというわけでもなさそうな気がします。 ちなみに、ちゃんと調べられていないのですが、Ruby や Kotlin には staticmethod がないそうです。

# ◯ まとめ

classmethod と staticmethod は、使える時は積極的に使う。と個人的に思っています。





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

# Step 1. デコレータ

ちなみに @デコレータ と呼ばれるものです。

インスタンスに対する classmethod と staticmethod であればデコレータを使えば、簡単に実装できます。

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

しかし、上記の方針ではクラスに対しては動きません。 特にクラスからクラスメソッドを呼び出すことは、手も足も出せません。

# 上記の実装では
# この呼び出し方はできない。
クラス.クラスメソッド()

# Step 2. ディスクリプタ

このような挙動を実装するには、属性参照された時に起動する ディスクリプタ が必要になります。 次の記事で見ていきます、裏では使われたりしますが、頻繁に表に登場する機能ではないので、知らなくても大丈夫かなと思います。

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

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

# おわりに

ここまで以下のように見てきました。

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




Python のクラスメソッド classmethod と スタティックメソッド staticmethod ってなに?