Pythonでクラスを扱うと、@classmethodというデコレーターに出会います。これは@staticmethodや一般的なインスタンスメソッド(Instance Method)とは明確に区別される独自の目的を持っています。
この文章では、@classmethodが何であるか、そして@staticmethod及びインスタンスメソッドと比較して、どのような状況でどのように使用すべきかを明確に理解していきます。
1. メソッドの3つのタイプ:インスタンス vs. クラス vs. 静的
Pythonのクラスには基本的に3つのタイプのメソッドが存在します。これらの最大の違いはメソッドが最初の引数として何を受け取るかにあります。
class MyClass:
# 1. インスタンスメソッド (Instance Method)
def instance_method(self, arg1):
# 'self'はクラスのインスタンスを受け取ります。
# インスタンス属性(self.x)にアクセスして修正できます。
print(f"インスタンスメソッドが{self}と{arg1}で呼ばれました")
# 2. クラスメソッド (Class Method)
@classmethod
def class_method(cls, arg1):
# 'cls'はクラス自体(MyClass)を受け取ります。
# クラス属性にアクセスしたり、
# インスタンスを生成する'ファクトリー'として主に使用されます。
print(f"クラスメソッドが{cls}と{arg1}で呼ばれました")
# 3. 静的メソッド (Static Method)
@staticmethod
def static_method(arg1):
# 'self'や'cls'を受け取りません。
# クラスやインスタンスの状態とは無関係なユーティリティ関数です。
# この関数はクラス内部に'所属している'だけです。
print(f"静的メソッドが{arg1}で呼ばれました")
# メソッド呼び出しの例
obj = MyClass()
# インスタンスメソッド(インスタンスを通じて呼び出す)
obj.instance_method("こんにちは")
# クラスメソッド(クラスまたはインスタンスを通じて呼び出し可能)
MyClass.class_method("世界")
obj.class_method("世界") # これも可能ですが、クラスで呼び出すのが明確です。
# 静的メソッド(クラスまたはインスタンスを通じて呼び出し可能)
MyClass.static_method("Python")
obj.static_method("Python")
一目で比較
| 区分 | 最初の引数 | アクセス対象 | 主な用途 |
|---|---|---|---|
| インスタンスメソッド | self(インスタンス) |
インスタンス属性(self.name) |
オブジェクトの状態を操作またはアクセスする |
| クラスメソッド | cls(クラス) |
クラス属性(cls.count) |
代替コンストラクタ(ファクトリー) |
| 静的メソッド | なし | なし | クラスと論理的に関連するユーティリティ関数 |
2. @classmethodの核心活用:ファクトリーメソッド
@classmethodの最も重要かつ一般的な使用例は「代替コンストラクタ(Alternative Constructor)」または「ファクトリーメソッド(Factory Method)」を定義することです。
基本コンストラクタである__init__は通常、明確な引数を受け取ってオブジェクトを初期化しますが、場合によっては他の方法(例:文字列パース、辞書、特定計算値)でオブジェクトを生成する必要があります。
@classmethodを使用すると、__init__以外にクラスインスタンスを返す別の「エントリーポイント」を作成できます。

例:日付文字列からPersonオブジェクトを生成する
Personクラスがnameとageを受け取ると仮定します。もし「名前-生年」形式の文字列でもオブジェクトを生成したい場合は、どうすればよいでしょうか?
import datetime
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
print(f"こんにちは、私は{self.name}で{self.age}歳です。")
@classmethod
def from_birth_year(cls, name, birth_year):
"""
生年を受け取り年齢を計算してPersonインスタンスを生成します。
"""
current_year = datetime.datetime.now().year
age = current_year - birth_year
# 'cls'はPersonクラス自体を指します。
# return Person(name, age)と同じですが、
# 'cls'を使用すると継承時に利点があります。(後述)
return cls(name, age)
# 基本コンストラクタを使用
person1 = Person("アリス", 30)
person1.greet()
# @classmethodで作成した代替コンストラクタを使用
person2 = Person.from_birth_year("マッドハッター", 1995)
person2.greet()
出力結果:
こんにちは、私はアリスで30歳です。
こんにちは、私はマッドハッターで30歳です。(現在の年2025基準)
上記の例でfrom_birth_yearメソッドはclsという引数を通じてPersonクラス自体にアクセスします。そして計算されたageを使用してcls(name, age)を呼び出し、これは即ちPerson(name, age)を呼び出すことと同じであり、新しいPersonインスタンスを返します。
3. @classmethodと継承
「ただPerson(...)を呼び出せばいいのに、なぜわざわざcls(...)を使うのですか?」
@classmethodの真の力は継承で発揮されます。clsは現在メソッドが呼び出された_まさにそのクラス_を指します。
Personを継承するStudentクラスを作ると仮定します。
class Student(Person):
def __init__(self, name, age, major):
super().__init__(name, age) # 親クラスの__init__を呼び出す
self.major = major
def greet(self):
# メソッドのオーバーライド
print(f"こんにちは、私は{self.major}専攻の{self.name}で{self.age}歳です。")
# Studentクラスから親の@classmethodを呼び出します。
student1 = Student.from_birth_year("ジョン", 2005, "コンピュータ科学")
# あっ、TypeErrorが発生します!
# Student.from_birth_yearを呼び出すと、
# Person.from_birth_yearが実行され、
# この時'cls'は'Student'になります。
# つまり、return cls(name, age)はreturn Student(name, age)になります。
# しかしStudentの__init__は'major'引数が追加で必要です。
上記のコードはStudentの__init__がmajorを必要とするためエラーが発生します。しかし、核心はclsがPersonではなくStudentを指しているという点です。
もしfrom_birth_yearが@staticmethodだったり、内部でPerson(name, age)をハードコーディングしていた場合、Student.from_birth_yearを呼び出してもStudentオブジェクトではなくPersonオブジェクトが返されていたでしょう。
@classmethodはclsを介して子クラスが呼ばれたとしても正しい子クラスのインスタンスを生成することを保証します。(もちろん上記のように子クラスの__init__のシグネチャが変わる場合は、from_birth_yearもオーバーライドする必要があります。)
4. まとめ
@classmethodについてまとめると、以下のようになります。
-
@classmethodはcls(クラス自体)を最初の引数として受け取ります。 -
主な用途は「代替コンストラクタ(ファクトリーメソッド)」です。
__init__以外にオブジェクトを生成するさまざまな方法を提供する際に使用します。 -
継承と共に使用する際に強力です。 子クラスから呼び出しても
clsは正しい子クラスを指し、該当クラスのインスタンスを生成します。
@staticmethodはクラス/インスタンス状態が全く必要ないユーティリティ関数に、@classmethodはクラス自体にアクセスする必要があるファクトリーメソッドに、そして一般インスタンスメソッドはオブジェクトの状態(self)を扱う際に使用されることを覚えれば、コードをより明確でPythonらしく書くことができます。
コメントはありません。