閉じる

【Python】classmethod vs staticmethod|違い・メリット・アンチパターン

Pythonのclassmethodstaticmethodは、どちらも「インスタンスに依存しないメソッド」を書くための仕組みですが、役割や向いている用途は大きく異なります。

本記事では、両者とインスタンスメソッドの違いから、実務での使いどころ、設計上のメリット、よくあるアンチパターンまで、図解とサンプルコードを交えて詳しく解説します。

classmethodとstaticmethodの違いとは

classmethodとstaticmethodの基本的な役割

Pythonには、大きく分けて3種類のメソッドがあります。

インスタンスメソッド、クラスメソッド、スタティックメソッドです。

それぞれ、どのオブジェクトを起点に呼び出されるか、どの情報にアクセスできるかが異なります。

もっとも基本となるのはインスタンスメソッドで、個々のインスタンスの状態(self)にアクセスするために使います。

一方で、クラス全体に関係する処理や、インスタンスに依存しない処理を記述したい場合にclassmethodstaticmethodが登場します。

ここでのポイントは次の通りです。

  • classmethod
    クラスそのものを受け取る(cls)メソッドです。クラス属性にアクセスしたり、インスタンスを生成する「ファクトリ」として利用したりするのに向いています。
  • staticmethod
    特別な第1引数を取りません。クラスやインスタンスへの参照を自動では受け取らないため、本質的には「クラスの中に置かれた普通の関数」と考えると理解しやすいです。

classmethodとstaticmethodのシグネチャの違い

以下の簡単な例で、どのようにシグネチャ(引数)が異なるかを確認してみます。

Python
class Example:
    # インスタンスメソッド
    def instance_method(self, x):
        print("self:", self)
        print("x:", x)

    # クラスメソッド
    @classmethod
    def class_method(cls, x):
        print("cls:", cls)
        print("x:", x)

    # スタティックメソッド
    @staticmethod
    def static_method(x):
        print("x:", x)


if __name__ == "__main__":
    obj = Example()

    # インスタンスメソッドはインスタンスから呼び出すのが基本
    obj.instance_method(10)

    # クラスメソッドはクラスから呼び出すのが基本
    Example.class_method(20)

    # スタティックメソッドはクラスから呼び出してもインスタンスから呼び出しても挙動は同じ
    Example.static_method(30)
    obj.static_method(40)
実行結果
self: <__main__.Example object at 0x...>
x: 10
cls: <class '__main__.Example'>
x: 20
x: 30
x: 40

出力からも分かるように、インスタンスメソッドではselfにインスタンスが、クラスメソッドではclsにクラスが渡されている一方、スタティックメソッドは普通の関数と同様に引数xしか受け取っていません。

インスタンスメソッドとの違い

インスタンスメソッドと、クラスメソッド/スタティックメソッドの最も大きな違いは、「インスタンスの状態に直接アクセスするかどうか」です。

インスタンスメソッドはselfを通じてインスタンス属性にアクセスできるので、オブジェクトごとに異なる状態を使った処理に向いています。

Python
class Counter:
    def __init__(self):
        self.count = 0  # インスタンスごとの状態

    def increment(self):
        self.count += 1

    def get_value(self):
        return self.count


if __name__ == "__main__":
    c1 = Counter()
    c2 = Counter()

    c1.increment()
    c1.increment()
    c2.increment()

    print("c1:", c1.get_value())
    print("c2:", c2.get_value())
実行結果
c1: 2
c2: 1

これに対して、クラスメソッドはclsを通じてクラス属性にアクセスします。

クラス全体で共有する設定値や、インスタンス生成のルールを扱う際に適しています。

スタティックメソッドは、インスタンスにもクラスにも自動的にはアクセスしません。

そのため、「オブジェクトの状態に依存しない純粋な処理」だけを書く場所として使うのが基本になります。

classmethodの使いどころとメリット

ファクトリメソッドにclassmethodを使う理由

classmethodの代表的な用途は「ファクトリメソッド」です。

ファクトリメソッドとは、「インスタンスを生成する別名のコンストラクタ」のようなもので、__init__では表現しづらい複雑な生成ロジックを分離するために使われます。

Python
from datetime import datetime


class User:
    def __init__(self, name: str, joined_at: datetime):
        # 実際のコンストラクタは、できるだけシンプルに保つ
        self.name = name
        self.joined_at = joined_at

    @classmethod
    def from_iso_date(cls, name: str, joined_at_str: str) -> "User":
        """ISO形式の文字列からUserを生成するファクトリメソッド"""
        # 事前処理: 文字列からdatetimeへ変換
        joined_at = datetime.fromisoformat(joined_at_str)
        # clsを使うことで、サブクラスから呼んでも正しくそのクラスを生成できる
        return cls(name, joined_at)


if __name__ == "__main__":
    # 通常のコンストラクタ
    user1 = User("Alice", datetime(2024, 1, 1))

    # 文字列から生成したいときはファクトリメソッドを使う
    user2 = User.from_iso_date("Bob", "2024-01-01T09:00:00")

    print(user1.name, user1.joined_at)
    print(user2.name, user2.joined_at)
実行結果
Alice 2024-01-01 00:00:00
Bob 2024-01-01 09:00:00

ポイントは、インスタンスを直接返すにもかかわらず、インスタンスメソッドではなくクラスメソッドを使っていることです。

これにより、cls(...)を使って柔軟にインスタンスを生成でき、後述する継承との相性も良くなります。

継承に強いクラスメソッドのメリット

classmethodの大きなメリットは「継承に強い」ことです。

ファクトリメソッド内でclsを使うことで、MyClassだけでなく、そのサブクラスから呼び出したときも、MyClassではなくサブクラス側のインスタンスを返せます。

Python
class Animal:
    def __init__(self, name: str):
        self.name = name

    @classmethod
    def from_config(cls, config: dict) -> "Animal":
        """設定(dict)からインスタンスを生成するファクトリ"""
        name = config.get("name", "unknown")
        # ここでclsを使うことが重要
        return cls(name)


class Dog(Animal):
    def __init__(self, name: str, breed: str):
        super().__init__(name)
        self.breed = breed

    @classmethod
    def from_config(cls, config: dict) -> "Dog":
        # 親クラスのロジックを再利用しつつ拡張してもよい
        name = config.get("name", "dog")
        breed = config.get("breed", "unknown")
        return cls(name, breed)


if __name__ == "__main__":
    dog_conf = {"name": "Pochi", "breed": "Shiba"}
    dog = Dog.from_config(dog_conf)

    print(type(dog))
    print(dog.name, dog.breed)
実行結果
<class '__main__.Dog'>
Pochi Shiba

もしファクトリメソッドがAnimal(...)Dog(...)のようにクラス名を「直書き」していた場合、サブクラス側での再利用が難しくなります。

クラスメソッド内では、クラス名ではなくclsを使うことにより、多態性(ポリモーフィズム)を活かした設計が可能になります。

コンストラクタ代替としてのclassmethod

Pythonには、JavaやC++のような「コンストラクタのオーバーロード」はありません。

そのため、「別の引数パターンでインスタンスを生成したい」場合に、複数のコンストラクタ代替としてクラスメソッドを用意するのが一般的です。

Python
import json
from dataclasses import dataclass


@dataclass
class Product:
    id: int
    name: str

    @classmethod
    def from_dict(cls, data: dict) -> "Product":
        return cls(id=int(data["id"]), name=str(data["name"]))

    @classmethod
    def from_json(cls, json_str: str) -> "Product":
        data = json.loads(json_str)
        return cls.from_dict(data)  # 既存のファクトリを再利用


if __name__ == "__main__":
    p1 = Product(1, "Apple")

    p2 = Product.from_dict({"id": "2", "name": "Orange"})

    p3 = Product.from_json('{"id": 3, "name": "Banana"}')

    print(p1)
    print(p2)
    print(p3)
実行結果
Product(id=1, name='Apple')
Product(id=2, name='Orange')
Product(id=3, name='Banana')

このように、クラスメソッドを「別名コンストラクタ」として設計することで、読みやすく拡張しやすいインターフェースを提供できます。

staticmethodの使いどころとメリット

クラスに紐づくユーティリティ関数としてのstaticmethod

staticmethodの役割は「インスタンスやクラスの状態を一切参照しない補助的な処理を、クラスの中にまとめておく」ことです。

このとき、その処理がクラスの責務と論理的に結びついていることが重要です。

Python
import re


class PasswordValidator:
    MIN_LENGTH = 8

    @staticmethod
    def _has_number(password: str) -> bool:
        """数字を含むかどうかを判定する補助関数"""
        return any(ch.isdigit() for ch in password)

    @staticmethod
    def _has_symbol(password: str) -> bool:
        """記号を含むかどうかを判定する補助関数"""
        return bool(re.search(r"[!@#$%^&*]", password))

    @classmethod
    def validate(cls, password: str) -> bool:
        """パスワードの強度チェック"""
        if len(password) < cls.MIN_LENGTH:
            return False
        if not cls._has_number(password):
            return False
        if not cls._has_symbol(password):
            return False
        return True


if __name__ == "__main__":
    print(PasswordValidator.validate("abc"))             # 弱い
    print(PasswordValidator.validate("abc12345"))        # 記号なし
    print(PasswordValidator.validate("abc12345!"))       # OK
実行結果
False
False
True

ここで_has_number_has_symbolは、インスタンスやクラス属性を一切使いません。

それでもPasswordValidatorという文脈にまとめておいたほうがコードを読む人にとって意味が分かりやすくなります。

このような「文脈上クラスに属する純粋関数」をまとめる場所としてstaticmethodは有効です。

名前空間を整理するためのstaticmethod

Pythonではクラスを「名前空間(ネームスペース)」として使うことができます。

特にオブジェクト指向設計を強く意識していないユーティリティ処理でも、「ある概念に関する関数」をひとまとまりにしたい場合があります。

そのようなときに、staticmethodを使ってクラスにグルーピングすることで、モジュール直下に関数が溢れるのを防げます。

Python
class StringUtils:
    @staticmethod
    def to_snake_case(name: str) -> str:
        """キャメルケースをスネークケースに変換する簡易関数"""
        result = ""
        for i, ch in enumerate(name):
            if ch.isupper() and i > 0:
                result += "_"
            result += ch.lower()
        return result

    @staticmethod
    def is_blank(text: str) -> bool:
        """空文字または空白のみかどうか"""
        return text.strip() == ""


if __name__ == "__main__":
    print(StringUtils.to_snake_case("UserName"))
    print(StringUtils.is_blank("   "))
    print(StringUtils.is_blank("abc"))
実行結果
user_name
True
False

ここでは、インスタンスを作る必要がないため、クラスを単なる「名前空間」として利用しています。

ただし、このパターンを乱用して「何でもかんでも1つのユーティリティクラスのスタティックメソッドにする」と、逆に見通しが悪くなるので、後述のアンチパターンも合わせて意識する必要があります。

モジュール関数との違いと使い分け

「statimethodにするか、モジュールレベルの関数にするか」は、Pythonでよく議論になるテーマです。

両者の技術的な能力はほとんど同じですが、「どこに属させると読み手にとって意味が明確か」という観点で使い分けるとよいです。

おおまかな指針としては次のように考えると整理しやすくなります。

種類向いているケース具体例
モジュール関数アプリケーション全体で汎用的に再利用される処理日付変換、ファイルパス操作、数値変換など
staticmethod特定のクラスやドメインの文脈に強く結びついた処理特定のエンティティのバリデーション、内部表現変換など

「どのクラスにも属さない、純粋な汎用処理」であれば、素直にモジュールレベルの関数として定義したほうが読みやすく、テストもしやすいです。

一方で、その処理が特定のクラスの内部ルールや仕様に依存しているのであれば、そのクラスのstaticmethodとして定義するほうが、コードリーディング時に意味が伝わりやすくなります。

classmethodとstaticmethodのアンチパターン

なんでもstaticmethodにするアンチパターン

よくある誤りとして、「インスタンスを作らないなら全部staticmethodで良い」と考えてしまうパターンがあります。

この結果、巨大な「なんでもUtils」クラスが生まれ、クラスの責務が不明確になり、変更やテストが困難になります。

Python
class BadUtils:
    @staticmethod
    def send_email(...):
        ...

    @staticmethod
    def format_price(...):
        ...

    @staticmethod
    def read_config(...):
        ...

    # さらに続く多種多様な処理...

このような設計では、関数同士の関係性や依存が読み取りにくくなり、クラス名から責務を推測することも困難です。

インスタンスが不要な処理であっても、以下のように分割を検討するべきです。

  • ドメインごとにクラスやモジュールを分ける
  • OrderFormatterEmailSenderなど、責務が明確な名前を付ける
  • 本当にグローバルなユーティリティであれば、モジュールレベル関数にする

「インスタンスを作らないから」という理由だけでstaticmethodに逃がすのではなく、「この処理はどの文脈に属するのが自然か」を軸に設計することが重要です。

classmethodなのにclsを使わないアンチパターン

次によくあるのが、「とりあえず将来拡張しそうだからclassmethodにしておこう」という発想です。

結果として、clsを一切使わないクラスメソッドが量産されてしまうことがあります。

Python
class BadExample:
    @classmethod
    def sum_numbers(cls, a: int, b: int) -> int:
        # clsをまったく使っていない
        return a + b

このような場合、classmethodではなくstaticmethod、あるいはモジュール関数であるべきです。

clsを全く使わないclassmethodは、読み手を混乱させるだけでなく、「継承に強い」というclassmethodの本来の利点を活かしていません。

正しい形は次のいずれかです。

Python
class GoodExample:
    @staticmethod
    def sum_numbers(a: int, b: int) -> int:
        return a + b

あるいは、クラスに強く紐づかないのであれば、モジュール関数にしてしまうほうがすっきりする場合もあります。

Python
def sum_numbers(a: int, b: int) -> int:
    return a + b

「clsを使う必要があるかどうか」を、classmethod採用の判断基準にすると、不要な複雑さを避けやすくなります。

インスタンスメソッドで十分な処理に多用するアンチパターン

もう1つありがちなのが、「インスタンスの状態を操作する処理なのに、わざわざstaticmethodやclassmethodで書いてしまう」パターンです。

Python
class BadAccount:
    def __init__(self, balance: int):
        self.balance = balance

    @staticmethod
    def deposit(account: "BadAccount", amount: int) -> None:
        # インスタンスの状態を操作している
        account.balance += amount

これは見た目にはそれらしく動きますが、「誰の責務なのか」が分かりにくくなります。

本来、残高を増やす責務はAccountインスタンス自身が持つべきです。

Python
class Account:
    def __init__(self, balance: int):
        self.balance = balance

    def deposit(self, amount: int) -> None:
        """インスタンスメソッドとして自分の状態を更新する"""
        self.balance += amount

このように、インスタンスの状態を変更する処理はインスタンスメソッドにまとめることで、オブジェクト指向の利点(カプセル化・責務の明確化)を活かせます。

「selfを使うべき場面で、無理にstatic/classmethodにしない」ことが、読みやすいクラス設計の基本です。

テスタビリティを下げる誤った設計パターン

最後に、テスタビリティ(テストのしやすさ)を損なうstatic/classmethodの使い方について触れます。

よくある問題は、static/classmethodの中で、外部サービスやI/O処理を直接呼び出してしまうケースです。

Python
import requests


class BadOrderService:
    @staticmethod
    def send_order(order_data: dict) -> None:
        # 外部APIに直接POSTしている
        response = requests.post("https://api.example.com/orders", json=order_data)
        response.raise_for_status()

このような設計では、テスト時にrequests.postを差し替える必要があり、パッチやモックが複雑になりがちです。

また、BadOrderService自体を差し替えることも難しくなります。

よりテストしやすい設計にするためには、依存するオブジェクトを外部から注入できる形にするのが有効です。

static/classmethod一辺倒ではなく、インスタンスと組み合わせることが大切です。

Python
import requests
from typing import Protocol


class OrderApi(Protocol):
    def post_order(self, order_data: dict) -> None:
        ...


class RequestsOrderApi:
    def post_order(self, order_data: dict) -> None:
        response = requests.post("https://api.example.com/orders", json=order_data)
        response.raise_for_status()


class OrderService:
    def __init__(self, api: OrderApi):
        # 依存を注入することでテスト時にモック実装に差し替えやすくなる
        self._api = api

    def send_order(self, order_data: dict) -> None:
        self._api.post_order(order_data)


if __name__ == "__main__":
    service = OrderService(RequestsOrderApi())
    service.send_order({"id": 1})

テスト時にはOrderApiのモック実装を渡せばよく、HTTPリクエストを実際には飛ばさずに検証できます。

static/classmethodは「状態を持たない=テストしやすい」と誤解されがちですが、外部I/Oに強く結びつけてしまうと、かえってテストが難しくなります。

依存性注入(Dependency Injection)やプロトコル(インターフェース)と組み合わせて、テスタビリティを意識した設計を心がけましょう。

まとめ

classmethodとstaticmethodはどちらも「インスタンスに依存しない処理」を表現する仕組みですが、役割と得意分野ははっきり異なります。

classmethodはclsを通じてクラスにアクセスでき、ファクトリメソッドや継承に強いコンストラクタ代替として有用です。

一方、staticmethodはクラスの文脈に属する純粋関数をまとめ、名前空間を整理するのに向いています。

ただし、何でもかんでもstaticにしたり、clsを使わないclassmethodを乱立させたりすると、責務が曖昧になりテスタビリティも低下します。

「selfを使うならインスタンスメソッド」「clsが必要ならclassmethod」「それ以外はstaticかモジュール関数」という軸で選び、処理がどの文脈に属するかを意識して設計することが、読みやすく保守しやすいPythonコードへの近道です。

クラスとオブジェクト指向

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

URLをコピーしました!