@classmethod と @staticmethod の違い

Python のクラスには、通常のインスタンスメソッドに加えて、@classmethod@staticmethod という2種類の特殊なメソッドがあります。どちらも「インスタンスを作らずに呼べる」点では似ていますが、役割が違う。

通常のインスタンスメソッド

まず基本を確認します。インスタンスメソッドは第一引数に self を受け取り、インスタンスの状態にアクセスできます。

class Dog:
    def __init__(self, name):
        self.name = name
    
    def bark(self):
        print(f"{self.name} が吠えた")

dog = Dog("ポチ")
dog.bark()  # ポチ が吠えた

self を通じてインスタンス変数 name にアクセスしている。これが最も一般的なメソッドの形。

@staticmethod:クラスに属する単なる関数

@staticmethod は、クラスの名前空間に置かれた普通の関数です。selfcls も受け取らない。

class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b
    
    @staticmethod
    def is_even(n):
        return n % 2 == 0

print(MathUtils.add(3, 5))      # 8
print(MathUtils.is_even(4))    # True

インスタンスを作る必要がない。クラスやインスタンスの状態に一切依存しない処理を書くときに使います。

# インスタンスからも呼べるが、意味はない
utils = MathUtils()
print(utils.add(1, 2))  # 3(動くが冗長)

@classmethod:クラス自体を受け取る

@classmethod は第一引数にクラス自体(cls)を受け取ります。インスタンスではなくクラスの情報にアクセスできる。

class Dog:
    species = "Canis familiaris"
    
    def __init__(self, name):
        self.name = name
    
    @classmethod
    def get_species(cls):
        return cls.species

print(Dog.get_species())  # Canis familiaris

cls はクラス自体を指す。クラス変数へのアクセスや、クラスに関する操作を行うときに使います。

決定的な違い:継承時の挙動

@staticmethod@classmethod の違いが明確になるのは継承のとき。

class Animal:
    @staticmethod
    def static_info():
        return "Animal の staticmethod"
    
    @classmethod
    def class_info(cls):
        return f"{cls.__name__} の classmethod"

class Dog(Animal):
    pass

print(Dog.static_info())  # Animal の staticmethod
print(Dog.class_info())   # Dog の classmethod

@staticmethod はクラス情報を持たないので、常に同じ結果を返す。@classmethodcls として呼び出し元のクラスを受け取るため、サブクラスの情報を反映できる。

ファクトリメソッドとしての @classmethod

@classmethod の最も重要な用途がファクトリメソッドです。__init__ とは別の方法でインスタンスを生成したいときに使います。

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
    
    @classmethod
    def from_string(cls, date_string):
        year, month, day = map(int, date_string.split("-"))
        return cls(year, month, day)
    
    @classmethod
    def today(cls):
        import datetime
        t = datetime.date.today()
        return cls(t.year, t.month, t.day)

date1 = Date(2024, 1, 15)
date2 = Date.from_string("2024-01-15")
date3 = Date.today()

cls(...) でインスタンスを作っている点が重要。Date(...) とハードコードせず cls(...) と書くことで、サブクラスでも正しく動く。

class JapaneseDate(Date):
    def __init__(self, year, month, day):
        super().__init__(year, month, day)
        self.era = self._calc_era(year)
    
    def _calc_era(self, year):
        if year >= 2019:
            return "令和"
        elif year >= 1989:
            return "平成"
        return "昭和"

# cls が JapaneseDate になるので、JapaneseDate のインスタンスが返る
jdate = JapaneseDate.from_string("2024-01-15")
print(type(jdate))  # <class 'JapaneseDate'>
print(jdate.era)    # 令和

もし from_string 内で Date(year, month, day) とハードコードしていたら、サブクラスから呼んでも Date のインスタンスが返ってしまう。cls を使うことで継承に対応できます。

いつどちらを使うか

@staticmethod

クラスやインスタンスの状態に依存しない。単なるユーティリティ関数をクラスの名前空間にまとめたいとき。

@classmethod

クラス変数にアクセスしたい。ファクトリメソッドとして別のコンストラクタを提供したい。継承を考慮した設計をしたい。

迷ったら @classmethod を選ぶ方が安全。あとから継承が必要になっても対応できる。@staticmethod は「このメソッドはクラスと無関係」という意図を明示したいときに使います。

モジュールレベル関数との比較

@staticmethod は「クラスに属する必要があるのか?」という疑問を呼びます。

# staticmethod として定義
class StringUtils:
    @staticmethod
    def is_palindrome(s):
        return s == s[::-1]

# モジュールレベル関数として定義
def is_palindrome(s):
    return s == s[::-1]

どちらでも動く。@staticmethod にする理由は、関連する機能をクラスにまとめて名前空間を整理したいとき。機能的な違いはありません。

まとめ

@staticmethod

selfcls も受け取らない。クラスに置かれた普通の関数。継承しても挙動は変わらない。

@classmethod

cls としてクラス自体を受け取る。ファクトリメソッドの実装に最適。継承時にサブクラスの情報を反映できる。

実務では @classmethod の方が圧倒的によく使います。特にファクトリメソッドは頻出パターン。@staticmethod は「クラスと本当に無関係な関数」に限定して使うのがよいでしょう。