Pythonでクラスを学び始めると、@staticmethod
と@classmethod
の違いで迷いがちです。
この記事では両者の定義と役割、self
とcls
の意味、呼び出しの仕組み、継承時のふるまいまでを段階的に解説し、初心者でも自信をもって使い分けできるようになることを目指します。
@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
は束縛されず、ただの関数として呼ばれます。
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
入力値だけで完結し、self
やcls
に触れる必要のない処理は@staticmethod
が適しています。
クラスの中に置くことで発見性が上がり、関連性が明確になります。
クラス全体の文脈が必要なら@classmethod
クラス変数、設定、別のクラスメソッドに依存する処理は@classmethod
を選びます。
継承時にサブクラスで自然に上書きされる点が特に強力です。
代替コンストラクタやファクトリは@classmethod
from系やwith_default系のコンストラクタは@classmethod
で書くのが定石です。
戻り値でcls(...)
を呼ぶことで、継承先でも正しくサブクラスのインスタンスが生成されます。
モジュール関数との比較と選び方
- クラスに強く関連し、そのクラスの名前空間にあると探しやすいなら
@staticmethod
でまとめます。 - 再利用性を高めたい、テスト容易性を重視したい、
import
サイクルを避けたいなら、モジュール関数として外に出す選択も有力です。 - どちらでもよい場合は、利用者が見つけやすい場所に置くことが最優先です。
実践例とベストプラクティス
例1 バリデーション関数は@staticmethod
バリデーションは入力のみで判定できることが多く、@staticmethod
が適しています。
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
文字列や辞書など、別表現からインスタンスを生成する典型です。
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
サブクラスごとに既定値や設定が異なるケースで真価を発揮します。
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
で内包し、それ以外はモジュール関数として公開するのが無難です。
クラス側からそれを呼び出すと意図が明確になります。
# 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
経由で参照することでサブクラスの上書きを素直に反映できます。
迷った時の選択フロー
- その処理はインスタンス固有の状態が必要か(属性や
self
)?必要なら通常メソッド。 - クラスレベルの情報や継承時の多態性が必要か?必要なら
@classmethod
。 - どちらも不要なら純粋関数。クラスに強く関連するなら
@staticmethod
、そうでなければモジュール関数。
「インスタンス状態」「クラス文脈」「純粋関数」の3分類で考えると迷いにくくなります。
まとめ
@staticmethodは純粋関数をクラスに整理するための道具であり、@classmethodはクラス文脈や継承を踏まえた生成や設定に用いる道具です。
インスタンス状態が必要なら通常メソッド、クラス文脈が必要なら@classmethod
、どちらも不要なら@staticmethod
またはモジュール関数という基準で選べば、設計の一貫性が高まり、後から読む人にも分かりやすいコードになります。
今日からはこの指針で、代替コンストラクタはclassmethod、バリデーションはstaticmethodを合言葉に使い分けてみてください。