閉じる

もう迷わない:@staticmethodと@classmethodの使い分け(Python)

Pythonでクラスを学び始めると、@staticmethod@classmethodの違いで迷いがちです。

この記事では両者の定義と役割、selfclsの意味、呼び出しの仕組み、継承時のふるまいまでを段階的に解説し、初心者でも自信をもって使い分けできるようになることを目指します。

@staticmethodと@classmethodの違い

定義と役割の基本

@staticmethodはインスタンスもクラスも自動で受け取りません

そのため純粋なヘルパー関数をクラス名の空間に「整理整頓」する目的で使います。

一方で@classmethodは第1引数としてクラスオブジェクト(慣例名はcls)を受け取ります

クラス変数や別のクラスメソッドに触れながら、クラス全体の文脈で処理するときに使います。

次の表は要点の比較です。

観点@staticmethod@classmethod
受け取る第1引数なしcls(クラス)
インスタンス状態(self)使えない使えない
クラス変数明示参照で可能(MyClass.X)cls.Xで自然に可能
継承の多態性弱い強い(clsが呼び出し元クラスになる)
主用途純粋関数の整理代替コンストラクタ、ファクトリ、クラス全体の設定参照
呼び出し方クラス/インスタンスどちらからでも同じクラス/インスタンスどちらからでもclsが束縛される

selfとclsの違い

  • selfはインスタンス(個々のオブジェクト)を指し、インスタンス変数にアクセスできます。通常メソッドの第1引数です。
  • clsはクラスそのものを指し、クラス変数や他のクラスメソッドにアクセスできます。@classmethodの第1引数です。
  • @staticmethodにはselfもclsも渡されません。必要なら明示的に受け取る引数として書きます。

バインディングと呼び出し方

メソッドは属性にアクセスした瞬間の「バインディング」で振る舞いが変わります。

@classmethodはクラスに束縛され、clsが自動で入ります。

@staticmethodは束縛されず、ただの関数として呼ばれます。

Python
class Sample:
    v = 10

    @staticmethod
    def add(a, b):
        # 何にも束縛されない純粋関数
        return a + b

    @classmethod
    def describe(cls):
        # 呼び出したクラスがclsに入る
        return f"class={cls.__name__}, v={cls.v}"

s = Sample()

print(Sample.add(2, 3))     # クラスから呼ぶ
print(s.add(4, 5))          # インスタンスから呼んでも同じ
print(Sample.describe())    # クラス情報を参照
print(s.describe())         # インスタンスからでもclsはクラス
実行結果
5
9
class=Sample, v=10
class=Sample, v=10

クラス変数とインスタンス状態へのアクセス可否

@staticmethodはインスタンス状態に触れられず、クラス状態にも自動では触れられません

クラス変数に触れたい場合はMyClass.varのように明示的に参照します。

一方で@classmethodはclsを介してクラス変数や別のクラスメソッドを自然に呼べます

@staticmethodの内部からクラス変数を変更する設計は避けてください

変更が必要なら@classmethodを使うのが安全です。

初心者向け 使い分けの指針

状態に依存しない処理は@staticmethod

入力値だけで完結し、selfclsに触れる必要のない処理は@staticmethodが適しています。

クラスの中に置くことで発見性が上がり、関連性が明確になります。

クラス全体の文脈が必要なら@classmethod

クラス変数、設定、別のクラスメソッドに依存する処理は@classmethodを選びます。

継承時にサブクラスで自然に上書きされる点が特に強力です

代替コンストラクタやファクトリは@classmethod

from系やwith_default系のコンストラクタ@classmethodで書くのが定石です。

戻り値でcls(...)を呼ぶことで、継承先でも正しくサブクラスのインスタンスが生成されます。

モジュール関数との比較と選び方

  • クラスに強く関連し、そのクラスの名前空間にあると探しやすいなら@staticmethodでまとめます。
  • 再利用性を高めたい、テスト容易性を重視したい、importサイクルを避けたいなら、モジュール関数として外に出す選択も有力です。
  • どちらでもよい場合は、利用者が見つけやすい場所に置くことが最優先です。

実践例とベストプラクティス

例1 バリデーション関数は@staticmethod

バリデーションは入力のみで判定できることが多く、@staticmethodが適しています。

Python
import re

class User:
    # 年齢は0〜150の整数とする
    @staticmethod
    def is_valid_age(age: int) -> bool:
        return isinstance(age, int) and 0 <= age <= 150

    # 簡易なメール形式チェック
    @staticmethod
    def is_valid_email(email: str) -> bool:
        # 実運用ではより厳密なバリデータを検討してください
        return bool(re.match(r"^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$", email))

print(User.is_valid_age(20))
print(User.is_valid_age(200))
u = User()
print(u.is_valid_email("alice@example.com"))
print(u.is_valid_email("bad@@example"))
実行結果
True
False
True
False

同じクラスで使う補助関数なのでクラス内に置く意味がありますが、他の多くのクラスからも使う一般関数ならモジュール関数化も検討します。

例2 from系の代替コンストラクタは@classmethod

文字列や辞書など、別表現からインスタンスを生成する典型です。

Python
class Person:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age

    def __str__(self) -> str:
        return f"Person(name={self.name}, age={self.age})"

    @classmethod
    def from_csv(cls, s: str) -> "Person":
        # 例: "Alice, 21" をパースしてインスタンス生成
        name, age_str = s.split(",")
        return cls(name.strip(), int(age_str))

p = Person.from_csv("Alice, 21")
print(p)
実行結果
Person(name=Alice, age=21)

clsを使ってインスタンス化しているため、サブクラスで呼ぶとサブクラスのインスタンスが返ります

例3 継承での多態的ファクトリは@classmethod

サブクラスごとに既定値や設定が異なるケースで真価を発揮します。

Python
class Logger:
    default_prefix = "BASE"

    def __init__(self, prefix: str) -> None:
        self.prefix = prefix

    def __str__(self) -> str:
        return f"{self.__class__.__name__}({self.prefix})"

    @classmethod
    def with_default(cls) -> "Logger":
        # 呼び出したクラス自身の設定を参照してインスタンス化
        return cls(cls.default_prefix)

class FileLogger(Logger):
    default_prefix = "FILE"

l1 = Logger.with_default()
l2 = FileLogger.with_default()
print(l1, type(l1))
print(l2, type(l2))

# staticmethodで同じことをやろうとすると多態性が失われる例
class BadLogger:
    default_prefix = "BASE"

    def __init__(self, prefix: str) -> None:
        self.prefix = prefix

    @staticmethod
    def with_default() -> "BadLogger":
        # クラス情報がないので、明示的に書くと固定される
        return BadLogger(BadLogger.default_prefix)

class GoodChild(BadLogger):
    default_prefix = "CHILD"

b1 = BadLogger.with_default()
b2 = GoodChild.with_default()
print(type(b1), b1.prefix)
print(type(b2), b2.prefix)  # 期待はGoodChild/CHILDだが...
実行結果
Logger(BASE) <class '__main__.Logger'>
FileLogger(FILE) <class '__main__.FileLogger'>
<class '__main__.BadLogger'> BASE
<class '__main__.BadLogger'> BASE

@classmethodは継承の文脈で多態的に動くため、ファクトリや代替コンストラクタに最適です。

ユーティリティ関数のまとめ方

ユーティリティをどこに置くかはプロジェクトの可読性に直結します。

クラスに強く結びつく関数だけを@staticmethodで内包し、それ以外はモジュール関数として公開するのが無難です。

クラス側からそれを呼び出すと意図が明確になります。

Python
# utils.py
def slugify(title: str) -> str:
    # とても単純な例
    return title.lower().replace(" ", "-")

# models.py
from utils import slugify

class Article:
    def __init__(self, title: str) -> None:
        self.title = title
        self.slug = slugify(title)  # モジュール関数を呼ぶだけで十分

「どこにあると見つけやすいか」を基準に配置すると、後から読む人にも優しいコードになります。

よくある誤解とチェックリスト

selfを使わないメソッドは見直す

通常メソッドなのにselfを使っていない場合、本当にインスタンスに属す必要があるかを見直します。

状態に依存しないなら@staticmethodに、クラス文脈が必要なら@classmethodに置き換えられます。

@staticmethodでクラス状態に触ろうとしない

@staticmethodからクラス変数を変更するのは避けるべきです。

変更は@classmethod経由で行うと、テストや継承の観点で安全です。

継承を考えるなら@classmethodを選ぶ

後でサブクラス化する可能性があるなら、多態性を保てる@classmethodを優先します。

cls経由で参照することでサブクラスの上書きを素直に反映できます。

迷った時の選択フロー

  1. その処理はインスタンス固有の状態が必要か(属性やself)?必要なら通常メソッド。
  2. クラスレベルの情報や継承時の多態性が必要か?必要なら@classmethod
  3. どちらも不要なら純粋関数。クラスに強く関連するなら@staticmethod、そうでなければモジュール関数。

「インスタンス状態」「クラス文脈」「純粋関数」の3分類で考えると迷いにくくなります。

まとめ

@staticmethodは純粋関数をクラスに整理するための道具であり、@classmethodはクラス文脈や継承を踏まえた生成や設定に用いる道具です。

インスタンス状態が必要なら通常メソッド、クラス文脈が必要なら@classmethod、どちらも不要なら@staticmethodまたはモジュール関数という基準で選べば、設計の一貫性が高まり、後から読む人にも分かりやすいコードになります。

今日からはこの指針で、代替コンストラクタはclassmethod、バリデーションはstaticmethodを合言葉に使い分けてみてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!