pythonでも他の言語と同じようなクラスメソッドとスタティックメソッドが使用できますが、python においては、クラスメソッドとスタティックメソッドが似ていながら別物になっていますので、少し注意が必要です。
クラスメソッド
クラスメソッドは、デコレータ @classmethod
を使用して定義されるメソッドです。通常のインスタンスメソッドでは、第一引数にインスタンス自身 (self) をとりますが、クラス自身 にあたる cls
を受け取ります。この cls
引数を通じて、クラスの属性や他のクラスメソッドにアクセスしたり、新しいインスタンスを作成したりすることができます。
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のインスタンスを作成し、それを返しています。次のように利用します。
mydoc = Document.from_file_path("/app/doc")
こうした機能をファクトリメソッドなどとも呼びますが、継承関係のサブクラスでも利用することができることを、少しだけ現実的な例で示します。
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
を使用して定義されるメソッドです。クラスやインスタンスの状態に依存せず、独立して機能するユーティリティ関数のようなものです。第一引数に self
も cls
も受け取りません。純粋に論理的なグルーピングのためにクラス内に配置されることが多く、クラスの外部にあっても同じように機能します。
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 を利用しています。クラスの状態には全く依存していません。
class Username:
def __init__(self, name):
self.name = self.normalize(name)
@staticmethod
def normalize(name):
return name.strip().lower().replace(" ", "_")
この例ではユーザー名(英名)を小文字でスペースをアンダーバーに変えるという正規化を行っています。
class ImageFileHandler:
@staticmethod
def is_supported(filename):
return filename.lower().endswith((".jpg", ".png", ".gif"))
この例でも staticmethod はクラスの状態に直接の依存はありませんが、このクラスのメソッドとしてまとめることはいたって自然です。
使い分けのまとめ
クラスメソッドを使うべきケース
- オルタナティブコンストラクタ (代替コンストラクタ):
クラスのインスタンスを生成する際に、__init__
とは異なる方法で初期化したい場合によく使用されます。例えば、ファイルからデータを読み込んでインスタンスを生成したり、特定の形式の文字列からインスタンスを生成したりする場合などです。 - クラス属性にアクセスまたは変更する場合:
クラス全体の共有データや設定を変更したり、参照したりするメソッドを定義する場合です。 - サブクラスで振る舞いを変更したい場合 (ポリモーフィズム):
基底クラスのクラスメソッドをサブクラスがオーバーライドすることで、cls
がサブクラスを指すため、サブクラス固有の処理を実行させることができます。
スタティックメソッドを使うべきケース:
- クラスに論理的に関連するが、クラスやインスタンスのデータにアクセスしないユーティリティ関数:
例えば、数学的な計算、文字列のフォーマット、データの検証など、特定のクラスに関連するが、そのクラスのインスタンスやクラス変数に依存しない汎用的な機能を提供したい場合です。 - 単独の関数としても定義できるが、クラスのコンテキストで管理したい場合:
関連する関数をクラス内にまとめることで、コードの可読性や構成の論理性を向上させたい場合に利用します。
コメント