HOWTO · Python

Python の静的クラス

Python の静的メソッド、クラスメソッド、モジュールレベル関数の使い分けと、それぞれがクラスやインスタンスの状態をどう扱うかを解説します。

このページの内容

Python には、他の言語にあるような静的クラスを宣言する組み込みの仕組みはありません。Python のクラスは引き続きインスタンス化や継承ができ、通常の属性も使えます。関連する @staticmethod と @classmethod は、メソッドの束縛方法を示すデコレーターであり、クラスを静的にするものではありません。呼び出し可能なユーティリティをまとめたい場合は、関数が何を必要とするかに応じて、静的メソッド、クラスメソッド、モジュールから選びます。

以下の例はそれぞれ単独で実行でき、Python が各種類のメソッドをどのように束縛するかを示します。

Python に静的クラスはある?

ありません。Python には、インスタンス化や継承を禁止する組み込みの静的クラス機能はありません。メソッドの大半を静的メソッドにしたクラスは定義できますが、それは設計上の慣習であり、別種のクラスではありません。オブジェクトを作る必要がないなら、モジュールを名前空間として使うほうが簡潔な場合があります。インスタンスやクラスレベルの振る舞いに価値があるときは、クラスを使います。

クラスに関連するユーティリティ関数には @staticmethod を使う

@staticmethod には、自動で最初の引数が渡されません。Python は self も cls も渡しません。クラスまたはインスタンスを通じて呼び出せます。グローバル名を参照したり、クラス名を使ってクラス属性にアクセスしたりできますが、呼び出しに応じて選ばれるクラスは自動では渡されません。そのため、サブクラスで振る舞いを上書きする用途ではクラスメソッドのほうが便利です。オブジェクトにもクラス固有の振る舞いにも依存しない処理で、クラスに関連付けておくのが自然な場合に使います。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

class Temperature:
    @staticmethod
    def celsius_to_fahrenheit(value):
        return value * 9 / 5 + 32

print(Temperature.celsius_to_fahrenheit(20))
68.0

同じメソッドはインスタンスからも呼び出せますが、クラスから呼び出すほうがインスタンスに依存しないことが分かりやすくなります。その関数がクラスと実質的に関係しないなら、モジュールレベルの関数のほうが再利用しやすい場合があります。

クラスに応じて振る舞いを変えるには @classmethod を使う

@classmethod には、クラスが cls として渡されます。代替コンストラクター、クラス設定、または呼び出しに使われたクラスに応じて変えるべき振る舞いに適しています。呼び出し元のクラスが渡されるため、サブクラスはファクトリーを継承しながら、自分自身の型のインスタンスを作れます。

このセクションのコードと表示されるラベルや出力は英語のままです。

class Record:
    def __init__(self, value):
        self.value = value

    @classmethod
    def from_text(cls, text):
        return cls(text.strip())

class NamedRecord(Record):
    pass

item = NamedRecord.from_text("  sample  ")
print(type(item).__name__, item.value)
NamedRecord sample

ここでは cls がサブクラスなので、継承したメソッドは NamedRecord を作ります。一方、ファクトリーの中で Record(...) を直接使うと、常に基底クラスが作られます。クラスメソッドは、単に静的メソッドの名前を変えたものではありません。束縛されたクラス自体が振る舞いの一部です。

クラスではなく名前空間が必要ならモジュールを使う

モジュールは、名前をひとつの名前空間にまとめます。ここで説明する通常のドメインクラスのようなインスタンス化、継承、インスタンスごとの振る舞いは提供しません。関連する関数をモジュールのトップレベルに定義し、名前空間を通じて呼び出したいときにそのモジュールをインポートします。次の自己完結した例では、単独で実行できるよう、一時ディレクトリに小さなモジュールを作成します。

このセクションのコードと表示されるラベルや出力は英語のままです。

import importlib
import sys
import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    module_file = Path(directory) / "number_tools.py"
    module_file.write_text("def double(value):\n    return value * 2\n", encoding="utf-8")
    sys.path.insert(0, directory)
    try:
        number_tools = importlib.import_module("number_tools")
        print(number_tools.double(6))
    finally:
        sys.path.remove(directory)
        sys.modules.pop("number_tools", None)
12

プロジェクトでは関数を通常の .py ファイルに置き、そのままインポートします。一時ファイルを使うのは、この例を単独で実行できるようにするためだけです。モジュールは静的クラスではありません。Python のモジュールはオブジェクトであり、その型をサブクラス化できますが、通常のドメインクラスのようなインスタンス化、継承、インスタンスごとの振る舞いを提供するものではありません。

静的メソッド、クラスメソッド、モジュールの選び方

処理がひとつのオブジェクトの状態を読み取ったり変更したりするなら、インスタンスメソッドを使います。サブクラスに応じた生成やクラスレベルの設定など、処理にクラス自体が必要なら @classmethod を選びます。暗黙のオブジェクト引数もクラス引数も必要とせず、クラスにまとめておくのが適切なヘルパー関数には @staticmethod を使います。名前空間だけで十分で、クラスの仕組みを使うと余計な手間が増えるなら、モジュールレベルの関数を選びます。

静的メソッドはクラス名を明示すればクラスデータを参照できますが、クラスが自動で渡されるわけではありません。サブクラス側からデータや振る舞いを提供させたい場合は、クラスメソッドを使います。インスタンスから静的メソッドを呼び出すこともできますが、その呼び出しによってメソッドがインスタンスにアクセスできるようにはなりません。つまり、静的メソッドからインスタンス変数を使えるかというよくある疑問への答えは、呼び出し元が必要な値を通常の引数として渡さない限り使えない、です。