Python | OOP:クラスメソッド

Python Python
スポンサーリンク

概要(クラスメソッドは「クラスに紐づく操作」—工場・設定・系統別挙動に効く)

@classmethodは、第1引数にインスタンスではなくクラス自身(cls)を受け取るメソッドです。インスタンスの状態には触れず、「クラス全体」に関わる操作(代替コンストラクタ、共有設定の更新、系統別の生成ロジック)を担います。初心者が押さえるべき核心は、インスタンスメソッド・スタティックメソッドとの違い、clsの活用、継承での「系統ごとの挙動切り替え」、そしてクラス変数との安全な連携です。


基本構文(@classmethodとcls、ほかのメソッドとの違い)

3つのメソッドの違い(直感と用途)

  • インスタンスメソッド: 第1引数はself。個体の状態を読む・書く。「オブジェクトができること」。
  • クラスメソッド: 第1引数はcls。クラス全体の操作。「設計図ができること」。
  • スタティックメソッド: 引数にself/clsを取らない。関連する“純粋な関数”を置く場所。
class User:
    def __init__(self, uid: str, name: str | None = None):
        self.uid = uid
        self.name = name or "unknown"

    @classmethod
    def from_dict(cls, data: dict) -> "User":
        return cls(str(data["id"]), data.get("name"))  # 代替コンストラクタ

    @staticmethod
    def slugify(s: str) -> str:
        return s.lower().replace(" ", "-")
Python

clsを使う理由(継承に強い工場)

clsは「呼び出した系統(サブクラス含む)」を指します。newするときにcls(…)を使えば、親ではなく子の型で生成され、継承後も自然に拡張が効きます。


実践パターン(Web / APIでよく使う“工場・設定・系統別”)

代替コンストラクタ(JSON・環境変数から安全に生成)

外界のデータ形式を受け取って、内部の形へ安全に変換してからインスタンスを作ると、入口の整合性が保てます。

class Settings:
    def __init__(self, base_url: str, api_key: str, timeout: float = 5.0):
        if not base_url.startswith("https://"):
            raise ValueError("HTTPSのみ許可")
        self.base_url = base_url
        self.api_key = api_key
        self.timeout = timeout

    @classmethod
    def from_env(cls) -> "Settings":
        import os
        base = os.getenv("BASE_URL") or "https://api.example.com"
        key = os.getenv("API_KEY") or ""
        if not key:
            raise RuntimeError("API_KEY未設定")
        return cls(base, key)  # 継承に強い生成
Python

共有設定の更新(クラス変数とクラスメソッド)

クラス全体で使うデフォルト値の更新入口を用意すると、検証と変更が一箇所に集約されます。

class Client:
    default_timeout = 5.0

    @classmethod
    def set_default_timeout(cls, v: float):
        if v <= 0:
            raise ValueError("timeoutは正の数")
        cls.default_timeout = v

    def __init__(self, base_url: str, api_key: str, timeout: float | None = None):
        import requests
        self.base_url = base_url
        self.timeout = timeout if timeout is not None else type(self).default_timeout
        self.session = requests.Session()
        self.session.headers.update({"Authorization": f"Bearer {api_key}"})
Python

系統別クライアント(継承+クラス属性の上書き)

クラスメソッドは継承と相性が良く、系統別の設定や生成ロジックに自然対応します。

class BaseClient:
    base_url = "https://api.example.com"
    @classmethod
    def with_key(cls, key: str) -> "BaseClient":
        return cls(cls.base_url, key)

    def __init__(self, base_url: str, key: str):
        self.base_url = base_url
        self.key = key

class EUClient(BaseClient):
    base_url = "https://eu.api.example.com"

eu = EUClient.with_key("KEY")  # clsはEUClient、生成もEUClient
Python

重要ポイントの深掘り(継承・cls・クラス変数の扱い)

clsを返す/生成に使う(置き換え可能性を守る)

  • 狙い: 親のコードを書き換えなくても、子でクラス属性・初期化を上書きすれば、その系統の工場が自動で子を返す。
  • 効果: 「親で用意した入口を、子でも自然に使える」ので拡張が壊れにくい。
class Token:
    @classmethod
    def issue(cls, raw: str) -> "Token":
        return cls(raw.strip())
Python

クラス変数は“不変寄せ”、可変はインスタンスへ

  • 安全性: list/dictなどの可変をクラス変数に置くと全インスタンスで共有され事故の元。クラス変数は定数・軽いデフォルトに限定。
  • 更新入口: クラスメソッドで検証してから変更すると、予期せぬ“影”や不整合を避けられる。

スタティックメソッドとの差分(いつどちらを使うか)

  • @classmethod: 継承フレンドリーな工場・共有設定・レジストリ操作など「クラス文脈」が必要。
  • @staticmethod: そのクラスに「論理的に属する」けれど、状態やクラス文脈に依存しない補助関数。

よくある落とし穴と回避(インスタンス状態の誤操作・互換性破り)

クラスメソッド内でインスタンス状態をいじろうとする

clsにはインスタンスの状態がありません。必要なら「新しく作る(cls(…))」「引数で受ける」どちらかにします。

親の契約を壊す代替コンストラクタ

from_xxxで受け取る引数の意味・戻り値の型は、親の意図(生成の安全)を守る。子の拡張は「追加引数にデフォルトを与える」「クラス属性で切替」のほうが互換性を保てます。

クラス変数の可変共有で連動事故

カウンタやキャッシュをクラス変数で持つと多スレッド・多プロセスで破綻しやすい。必要なら専用の管理クラス・外部ストア(DB、KV)へ寄せるのが安全です。


例題(Webミニアプリ:設定工場→クライアント生成→サービス)

設定工場で“どこでも同じ作り方”

class Settings:
    def __init__(self, base_url: str, api_key: str, timeout: float = 5.0):
        self.base_url = base_url
        self.api_key = api_key
        self.timeout = timeout

    @classmethod
    def from_env(cls) -> "Settings":
        import os
        base = os.getenv("BASE_URL", "https://api.example.com")
        key = os.getenv("API_KEY")
        if not key:
            raise RuntimeError("API_KEY未設定")
        return cls(base, key)
Python

クライアントを“設定から作る”クラスメソッド

import requests

class Client:
    ua: str = "MyApp/1.0"

    def __init__(self, s: Settings):
        self.base_url = s.base_url
        self.timeout = s.timeout
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {s.api_key}",
            "User-Agent": type(self).ua,
        })

    @classmethod
    def from_settings(cls, s: Settings) -> "Client":
        return cls(s)  # 継承に強い工場

    def get(self, path: str) -> dict:
        r = self.session.get(f"{self.base_url}{path}", timeout=self.timeout)
        r.raise_for_status()
        return r.json()
Python

サービスはクライアントを受けて“使うだけ”

class UserService:
    def __init__(self, client: Client):
        self.client = client

    def profile(self, uid: str) -> dict:
        data = self.client.get(f"/users/{uid}")
        return {"id": str(data.get("id", "")), "name": data.get("name") or "unknown"}

# つなぐ
s = Settings.from_env()
cli = Client.from_settings(s)
svc = UserService(cli)
Python

まとめ(「クラスに属する操作」は@classmethodで集約する)

@classmethodは、クラス文脈を扱うための入口です。clsで継承に強い工場を作り、共有設定の更新はクラスメソッドへ集約。クラス変数は不変寄せにして、可変はインスタンス側へ。スタティックメソッドは「純粋な補助」に限定し、役割を明確に分ける。これを型として徹底すれば、初心者でも「拡張に強く、壊れにくく、読みやすい」クラス設計が自然に書けるようになります。

タイトルとURLをコピーしました