python クラスメソッドとスタティックメソッド

python Python

pythonでも他の言語と同じようなクラスメソッドとスタティックメソッドが使用できますが、python においては、クラスメソッドとスタティックメソッドが似ていながら別物になっていますので、少し注意が必要です。

クラスメソッド

クラスメソッドは、デコレータ @classmethod を使用して定義されるメソッドです。通常のインスタンスメソッドでは、第一引数にインスタンス自身 (self) をとりますが、クラス自身 にあたる cls を受け取ります。この cls 引数を通じて、クラスの属性や他のクラスメソッドにアクセスしたり、新しいインスタンスを作成したりすることができます。

Python
import datetime

class Document:
    def __init__(self, filename: str, category: str, uploaded_at: datetime.datetime = None):
        self.filename = filename
        self.category = category
        self.uploaded_at = uploaded_at or datetime.datetime.now()

    @classmethod
    def from_file_path(cls, path: str):
        import os
        basename = os.path.basename(path)
        category = "general"  # default
        return cls(filename=basename, category=category)

classmethod は、そのクラスのインスタンスを生成するためによく使われます。この例では、クラスメソッドにファイルのパス名を渡してカテゴリは既定、upload日付は現在としてDocumentのインスタンスを作成し、それを返しています。次のように利用します。

Python
mydoc = Document.from_file_path("/app/doc")

こうした機能をファクトリメソッドなどとも呼びますが、継承関係のサブクラスでも利用することができることを、少しだけ現実的な例で示します。

Python
class User:
    def __init__(self, username: str, access_level: int):
        self.username = username
        self.access_level = access_level

    @classmethod
    def from_json(cls, data: dict):
        return cls(
            username=data["username"],
            access_level=data.get("access_level", 1)  # デフォルトアクセスレベル
        )

    def describe(self):
        return f"User: {self.username}, Access: {self.access_level}"

class Admin(User):
    def __init__(self, username: str, access_level: int = 10):
        super().__init__(username, access_level)

    def describe(self):
        return f"Admin: {self.username}, Access: {self.access_level}"

admin_data = {
    "username": "john lenon",
    "access_level": 10
}

# サブクラスでクラスメソッドを呼び出せば、戻るのはサブクラスのインスタンス
admin = Admin.from_json(admin_data)

print(type(admin))        # <class '__main__.Admin'>
print(admin.describe())    # Admin: asnr_admin, Access: 10

このように親クラスで定義されたクラスメソッドによるファクトリを子クラスであるAdminでも利用することができます。

スタティックメソッド

スタティックメソッドは、デコレータ @staticmethod を使用して定義されるメソッドです。クラスやインスタンスの状態に依存せず、独立して機能するユーティリティ関数のようなものです。第一引数に selfcls も受け取りません。純粋に論理的なグルーピングのためにクラス内に配置されることが多く、クラスの外部にあっても同じように機能します。

Python
class EmailAccount:
    def __init__(self, email):
        if not self.is_valid_email(email):
            raise ValueError("Invalid email format")
        self.email = email

    @staticmethod
    def is_valid_email(email):
        return "@" in email and "." in email

この例ではEmailアドレスのフォーマットチェックのために staticmethod を利用しています。クラスの状態には全く依存していません。

Python
class Username:
    def __init__(self, name):
        self.name = self.normalize(name)

    @staticmethod
    def normalize(name):
        return name.strip().lower().replace(" ", "_")

この例ではユーザー名(英名)を小文字でスペースをアンダーバーに変えるという正規化を行っています。

Python
class ImageFileHandler:
    @staticmethod
    def is_supported(filename):
        return filename.lower().endswith((".jpg", ".png", ".gif"))

この例でも staticmethod はクラスの状態に直接の依存はありませんが、このクラスのメソッドとしてまとめることはいたって自然です。

使い分けのまとめ

クラスメソッドを使うべきケース

  • オルタナティブコンストラクタ (代替コンストラクタ):
    クラスのインスタンスを生成する際に、__init__ とは異なる方法で初期化したい場合によく使用されます。例えば、ファイルからデータを読み込んでインスタンスを生成したり、特定の形式の文字列からインスタンスを生成したりする場合などです。
  • クラス属性にアクセスまたは変更する場合:
    クラス全体の共有データや設定を変更したり、参照したりするメソッドを定義する場合です。
  • サブクラスで振る舞いを変更したい場合 (ポリモーフィズム):
    基底クラスのクラスメソッドをサブクラスがオーバーライドすることで、cls がサブクラスを指すため、サブクラス固有の処理を実行させることができます。

スタティックメソッドを使うべきケース:

  • クラスに論理的に関連するが、クラスやインスタンスのデータにアクセスしないユーティリティ関数:
    例えば、数学的な計算、文字列のフォーマット、データの検証など、特定のクラスに関連するが、そのクラスのインスタンスやクラス変数に依存しない汎用的な機能を提供したい場合です。
  • 単独の関数としても定義できるが、クラスのコンテキストで管理したい場合:
    関連する関数をクラス内にまとめることで、コードの可読性や構成の論理性を向上させたい場合に利用します。

コメント

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