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
>>> #
... # Step 2. 動作
... #
... インスタンス = クラス()
>>> 
>>> # インスタンスからの呼び出し方
... インスタンス.メソッド()              is インスタンス
True
>>> インスタンス.クラスメソッド()        is クラス
True
>>> インスタンス.スタティックメソッド()  is None
True
>>> 
>>> # クラスからの呼び出し方
... クラス.メソッド(インスタンス)        is インスタンス  # 補足
True
>>> クラス.クラスメソッド()              is クラス
True
>>> クラス.スタティックメソッド()        is None
True
>>> 

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


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

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

# ◯ 補足

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

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

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

クラスからメソッドを呼び出す時に、なぜ引数を与えないといけないかは、 以下の記事で確認してきました。 知らなくても先に読み進められるかなと思っています。

>>> # クラスからの呼び出し方
... クラス.メソッド(インスタンス)        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. クラスメソッド

クラスから呼び出すクラスメソッドについて考えます。 普通のメソッドをクラスから呼び出す場合は、第一引数が必要になります。

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

一方で、クラスメソッドは、クラスから呼び出す場合は、第一引数は要求されません。 この機能、どこで使うのでしょうか?

クラス.クラスメソッド()              is クラス

# 2.1. 例えば...

1つのクラスに対して、複数のインスタンス化する方法を定義しいたいときがあります。 例えば、いま学生 Student を定義するクラスについて考えます。

#
# 対話モード >>> に
# コピペで動きます。
#
import datetime

class Student:
    def __init__(self, name, age, registered_at):
        self.name = name
        self.age = age
        self.registered_at = registered_at
    
    def __repr__(self):
        tuple_ = (self.name, self.age, repr(self.registered_at))
        return "Student('%s', %d, %s)" % tuple_


student = Student('伊藤誠', 17, datetime.datetime.now())
print(student)
>>> student = Student('伊藤誠', 17, datetime.datetime.now())
>>> print(student)
Student('伊藤誠', 17, datetime.datetime(2019, 11, 25, 22, 13, 37, 653160))
>>>

# 2.2. ここで...

現在時刻で学生 Student をインスタンス化するメソッドを作りたいとします。 ここでは2通り考えられるかなと思います。 すいません、いい例が思い浮かびませんでした orz

# 方法1 場合分け

#
# 対話モード >>> に
# コピペで動きます。
#
import datetime

class Student:
    def __init__(self, name, age, registered_at=None):
        if registered_at is None:
            registered_at = datetime.datetime.now()
        self.name = name
        self.age = age
        self.registered_at = registered_at
    
    def __repr__(self):
        tuple_ = (self.name, self.age, repr(self.registered_at))
        return "Student('%s', %d, %s)" % tuple_


student = Student('伊藤誠', 17)
print(student)
>>> print(student)
Student('伊藤誠', 17, datetime.datetime(2019, 11, 25, 22, 28, 0, 228016))
>>>

# 方法2クラスメソッドを使う

#
# 対話モード >>> に
# コピペで動きます。
#
import datetime

class Student:
    def __init__(self, name, age, registered_at):
        self.name = name
        self.age = age
        self.registered_at = registered_at
    
    @classmethod
    def register_now(cls, name, age):
        registered_at = datetime.datetime.now()
        return cls(name, age, registered_at)
    
    def __repr__(self):
        tuple_ = (self.name, self.age, repr(self.registered_at))
        return "Student('%s', %d, %s)" % tuple_


student = Student.register_now('伊藤誠', 17)
print(student)
>>> print(student)
Student('伊藤誠', 17, datetime.datetime(2019, 11, 25, 22, 38, 44, 330153))
>>>

# 2.3. 第一引数 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.4. どっちがいいの?

すいません、もしかしたら、この例だと、クラスメソッドを使わない方が良いかもしれません... orz

以下の様な言葉があります。

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

もし Student クラスのオブジェクトをインスタンス化するにしても、 それが別々の意味を持つなら、__init__registered_at でメソッドを分けてあげた方が良いかなと思ったり、思わなかったりします。 これじゃわからないですよね...

# 2.5. ちなみに...

datatime.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)

datatime.datatime は時刻を表すクラスです。

import datetime
datetime.datetime(2019, 1, 1, 0, 0)
>>> import datetime
>>> datetime.datetime(2019, 1, 1, 0, 0)
datetime.datetime(2019, 1, 1, 0, 0)
>>> 

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

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

しかし datatime.datatime クラスではそのよな実装にはなっていません。 自分もわかっていないのですが、このあたりで雰囲気が伝わればなと思います。

# 2.6. まとめ

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

# 3. 意味があるの?

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


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

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


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

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

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

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

〜することができないようにする機能と言うのは、意味があるのでしょうか? クラスメソッドなら 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 と書いてまで副作用の範囲を限定しても、もはや焼け石に水感もあります。

# 理由 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 の実装につなげていきます。 知らなくても大丈夫です。




Last Updated: 12/14/2019, 1:04:14 AM