Pythonの「@classmethod」は、クラスに属するメソッドである「クラスメソッド」を、クラス内部に定義するための構文です。このように最初に「@」を付けて、メソッドの機能を調整するための構文を「デコレータ」と呼びます。
Pythonの@classmethodを使いこなすためには、このデコレータの使い方や、クラスメソッドの特徴を知っておくことが重要です。また、インスタンスメソッドとの違いや使い分け方、クラスメソッドの活用方法についても理解すると役立つでしょう。そこで本記事では、Pythonの@classmethod(クラスメソッド)について詳しく解説します。
目次
Pythonの「@classmethod」とは、クラスメソッドを定義するための「デコレータ」です。@classmethodを以下のように使用することで、Pythonでクラスメソッドが使えるようになります。
// サンプルプログラム # coding: UTF-8 # Sampleクラス|@classmethodの概要 class Sample: # クラスメソッドを定義する @classmethod def function(cls): print("クラスメソッドが呼び出されました") # main()|プログラムのエントリーポイント if __name__ == "__main__": # Sampleクラスのクラスメソッド「function」を呼び出す Sample.function() // 実行結果
クラスメソッドの構文や機能の詳細については、後ほど改めて解説します。しかし、そもそも「Pythonのクラスメソッドとは何か」「ほかのメソッドと何が違うか」など、分からないことも多いでしょう。そこで、@classmethodを使うために理解しておく必要がある知識について、まずは解説していきます。
Pythonの「メソッド」とは、クラスの内部で定義されている関数のことです。ほかのプログラミング言語では、メソッドと関数が同じものとして扱われていることもありますが、Pythonの場合は厳密には区別されています。
「関数」は、クラス内部では定義されていない、つまり特定のデータ型とは結びついていないことが特徴です。以下のサンプルプログラムで、メソッドと関数の違いを確認しておきましょう。
// サンプルプログラム # coding: UTF-8 # 「osライブラリ」をインポートする import os # 関数を定義する def function(): print("関数が呼び出されました") # Sampleクラス class Sample: # メソッドを定義する def function(self): print("メソッドが呼び出されました") # main()|プログラムのエントリーポイント if __name__ == "__main__": # function()関数を呼び出す function() # Sampleクラスをインスタンス化して、function()メソッドを呼び出す sample = Sample() sample.function() # osライブラリのgetcwd()関数を呼び出す print(os.getcwd()) // 実行結果
上記のサンプルプログラムでは、2つのfunction()を定義しています。一方はグローバルな領域、他方はクラス内で定義したものです。前者は特定のクラスと結びついていないため「関数」、後者はクラスと紐づいているため「メソッド」となります。
両者を区別する際に、分かりやすいポイントとなるのが「呼び出し方」です。関数は単に「関数名()」という構文ですが、後者は「インスタンス名もしくはクラス名.関数名()」とする必要があり、関数名だけでは呼び出せません。
またサンプルプログラムの最後にある「os.getcwd()」は、メソッドではなく関数です。「os」はクラス名ではなくモジュール名なので、間違えないようにしましょう。
なお、Pythonのメソッドを呼び出す際は、関数名だけでは呼び出せないと解説しました。その前に「インスタンス名」「クラス名」を付ける必要があるのですが、どちらが必要かはメソッドの種類によって異なります。Pythonのメソッドは、以下の3種類に分けることができます。
「インスタンスメソッド」は、クラスのインスタンスに紐づいているメソッドです。そのため、まずはインスタンスを生成したあとで、インスタンスを介して呼び出す必要があります。インスタンスメソッドは、以下の構文で定義します。
class クラス名: # インスタンスメソッドを定義する def メソッド名(self, 引数名):
通常の関数とは2つの点で異なります。1つ目は、前述したように「クラス内部で定義すること」です。2つ目は、第1引数が必ず「self」になるということです。
このselfは、自身のインスタンスにアクセスできる「インスタンス変数」であり、メソッドの呼び出し時に自動的に引き渡されます。クラス内のインスタンスオブジェクトには、このselfを介してアクセスします。なお、インスタンスメソッドの呼び出し方は、以下のとおりです。
# クラスをインスタンス化する インスタンス名 = クラス名() # インスタンスを介してメソッドを呼び出す インスタンス名.メソッド名(引数)
インスタンスメソッドは、クラスのインスタンスを介して呼び出さないといけません。また、メソッドの第1引数である「self」は、呼び出し時に自動的に引き渡されるため、プログラマが明示する必要はありません。インスタンスメソッドの使い方の詳細は、以下のサンプルコードのとおりです。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # インスタンスメソッドを定義する # 引数として整数値を取る def function(self, value): print("インスタンスメソッドが呼び出されました:" + str(value)) # main()|プログラムのエントリーポイント if __name__ == "__main__": # Sampleクラスをインスタンス化する sample = Sample() # Sampleクラスのインスタンスを介して、function()メソッドを呼び出す sample.function(100) // 実行結果
なお、インスタンスメソッドの第1引数は別名に変えても問題ありませんが、分かりづらくなるため「self」にしておきましょう。
「クラスメソッド」は、インスタンスメソッドとは異なり、インスタンスに紐づいていないメソッドです。そのため、インスタンスを作成しなくても、メソッドを呼び出すことができます。クラスメソッドの生成は、以下の構文で行います。
class クラス名: # クラスメソッドを定義する @classmethod def メソッド名(cls, 引数名):
メソッドを定義する前に、「@classmethod」と記載することがポイントです。また、インスタンスメソッドの第1引数がインスタンス変数「self」であったのに対し、クラスメソッドではクラスオブジェクトにアクセスできる「cls」となります。なお、デコレータやクラスオブジェクトなどの詳細については、後ほど改めて解説します。クラスメソッドの呼び出し方は、以下のとおりです。
# クラス名を指定してクラスメソッドを呼び出す クラス名.メソッド名(引数)
クラスメソッドは、インスタンスではなくクラスそのものに紐づいているため、クラス名を指定するだけで呼び出すことができます。なお、インスタンスメソッドと同様に、第1引数は自動的に引き渡されます。クラスメソッドの使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # クラスメソッドを定義する # 引数として整数値を取る @classmethod def function(cls, value): print("クラスメソッドが呼び出されました:" + str(value)) # main()|プログラムのエントリーポイント if __name__ == "__main__": # Sampleクラスのfunction()メソッドを呼び出す Sample.function(100) // 実行結果
クラスメソッドの第1引数を別名に変えても動作しますが、インスタンスメソッドの「self」と区別するためにも、「cls」を使うようにしましょう。
「スタティックメソッド」は、クラスとインスタンスのどちらにも属していない、孤立したメソッドです。どちらかというとクラスメソッドに近いですが、定義するための構文は以下のように異なります。
class クラス名: # スタティックメソッドを定義する @staticmethod def メソッド名(引数名):
重要なポイントは、デコレータが「@staticmethod」になっていることや、第1引数が必須でないことです。インスタンスメソッドとクラスメソッドでは、いずれもクラス内部のオブジェクトにアクセスするための変数を、第1引数として定義する必要がありました。しかし、クラス内部のオブジェクトにアクセスしないスタティックメソッドでは、そうした引数は不要となります。
なお、スタティックメソッドの呼び出し方は、前述したクラスメソッドと同じです。クラスメソッドの呼び出し方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # スタティックメソッドを定義する # 引数として整数値を取る @staticmethod def function(value): print("スタティックメソッドが呼び出されました:" + str(value)) # main()|プログラムのエントリーポイント if __name__ == "__main__": # Sampleクラスのfunction()メソッドを呼び出す Sample.function(100) // 実行結果
クラスメソッドとスタティックメソッドは、同じように感じられるかもしれません。両者の使い方については、後ほど改めて解説します。
Pythonのデコレータは、既存の関数・メソッドの処理を引き継いだうえで、機能の追加や変更を行うための仕組みです。大きなポイントは、既存の関数・メソッドの中身を変えずに、機能の調整ができることです。Pythonのデコレータについて、以下の3つのポイントから解説します。
Pythonのデコレータを使用するにあたっては、以下の3つが必要になります。
全体の流れをつかむために、まずは既存メソッドを「func_old」・新規メソッドを「func_new」として、以下のサンプルコードを実行してみましょう。
// サンプルプログラム # coding: UTF-8 # デコレータの対象となる既存メソッドを定義する def func_old(func): print("func_old") # 既存メソッドの処理を追加・変更する新規メソッドを定義する # デコレータには「既存メソッドの名前」を指定する @func_old def func_new(): print("func_new") # func_newを実行する # 間接的にfunc_oldが呼び出されることになる func_new // 実行結果
重要なポイントは、デコレータを付加したfunc_newを呼び出すと、func_newの内部は実行されず、既存メソッドのfunc_oldが実行されることです。「@func_old」と記載することで、func_oldがデコレータの対象になったことが理由です。その結果、func_newを呼び出したとしても、デコレータの対象であるfunc_oldが間接的に実行されます。
func_oldの第1引数「func」には、func_new自身が引き渡されます。なお、既存メソッドのfunc_oldの第1引数に「func」を追加しなかったり、func_newの呼び出し時にカッコを付けたりするとエラーとなります。
func_newの処理内容を実行させたい場合は、func_oldの内部でfunc_newを実行させるようにします。前述したように、func_oldの第1引数にはfunc_new自体が「func」として引き渡されるため、func_oldからfuncを呼び出せばOKです。以下のサンプルコードで詳細を確認しましょう。
// サンプルプログラム # coding: UTF-8 # デコレータの対象となる既存メソッドを定義する def func_old(func): print("func_old") # 第1引数を利用して新規メソッドを呼び出す func() # 既存メソッドの処理を追加・変更する新規メソッドを定義する # デコレータには「既存メソッドの名前」を指定する @func_old def func_new(): print("func_new") # func_newを実行する # 間接的にfunc_oldが呼び出されることになる func_new // 実行結果
このように、func_oldの内部で第1引数の「func」を明示的に呼び出すと、func_newを実行することができます。つまり、デコレータを使用してメソッドを定義することで、デコレータに指定したメソッドが間接的に呼び出され、なおかつ追加したメソッドも呼び出されるということです。
デコレータには、そのほかにも便利な機能があるため、上手く使いこなすとPythonプログラミングの効率化に役立ちます。しかし、「@classmethodデコレータ」を使いこなすためには、ここまで紹介した内容を理解できていればOKです。
@classmethodやクラスメソッドの構文については、前述したとおりで単純なものです。しかし、「@classmethod」はいったいどのメソッドを呼び出すデコレータなのでしょうか。ここを理解できていなければ、釈然としないままクラスメソッドを使うことになってしまいます。
実は@classmethodは、Pythonの組み込み関数「classmethod()」を呼び出すためのものなのです。classmethod()は、メソッドをクラスメソッドに変換できる組み込み関数です。
メソッドをクラスメソッドに変換できる「classmethod()」は、以下の構文で使用します。
クラス名.クラスメソッド名 = classmethod(クラス名.メソッド名)
classmethod()の引数には、クラスメソッドに変換したいメソッドを指定します。こうすることで指定したメソッドがクラスメソッドに変換されて、その戻り値をクラスのアトリビュート(属性)に追加すると、クラスメソッドとして使えるようになります。なお、変換前と変換後のメソッド名は同じで構いません。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # メソッドを定義する # 引数として整数値を取る def function(cls, value): print("クラスメソッドが呼び出されました:" + str(value)) # main()|プログラムのエントリーポイント if __name__ == "__main__": # 組み込み関数「classmethod()」で、Sample.function()をクラスメソッドに変換する # 変換したメソッドを「function()」という名称で、Sampleクラスのアトリビュート(属性)に追加する Sample.function = classmethod(Sample.function) # Sampleクラスのクラスメソッド「Sample.function()」を呼び出す Sample.function(100) // 実行結果
上記のサンプルプログラムのようにclassmethod()を使用することで、Sample.function()をクラスメソッドに変換できます。ただし、変換前のメソッドの第1引数は、「cls」にしておく必要があるので注意してください。あとは通常どおりクラスメソッドとして呼び出すだけです。
なお、クラスメソッドとして変換したものを「Sample.function」に代入していますが、これはクラスのアトリビュート(属性)への追加です。Pythonでは以下のサンプルコードのように、クラスの外部からクラスに変数を追加することができます。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # 内部は実装しない pass # 外部の関数を定義する def function(value): print("メソッドが呼び出されました:" + str(value)) # main()|プログラムのエントリーポイント if __name__ == "__main__": # 組み込み関数「classmethod()」で、Sample.function()をクラスメソッドに変換する # 変換したメソッドを「function()」という名称で、Sampleクラスのアトリビュート(属性)に追加する sample = Sample() # 新しいアトリビュート(属性)として「value」を追加する sample.value = 100 # 新しいアトリビュート(属性)として「function」を追加する sample.function = function # 追加したfunctionメソッドを呼び出す sample.function(sample.value) // 実行結果
組み込み関数「classmethod()」で、クラスメソッドを作成できることが分かりました。前述したように、デコレータは指定された関数名を間接的に実行するための機能です。つまり@classmethodは、間接的にclassmethod()を呼び出すためのデコレータだということです。
# クラスを定義する class クラス名: # クラスメソッドを定義する @classmethod def メソッド名(cls, 引数): 処理内容 # クラスメソッドを呼び出す クラス名.メソッド名()
以上の例のように、「@classmethod」を付加してメソッドを定義することで、間接的にclassmethod()関数が呼び出されて、指定した名称のクラスメソッドが生成されます。デコレータを使用する場合は、コーディングがよりシンプルになることを覚えておきましょう。
そのため、Pythonでクラスメソッドを定義する際は、classmethod()ではなく@classmethodを使うことが基本です。このように、通常よりシンプルなソースコードで書ける構文のことを、「シンタックスシュガー」と呼びます。
スタティックメソッドを定義するための「@staticmethod」も、@classmethodと同様にメソッドを間接的に呼び出すためのデコレータです。@staticmethodはstaticmethod()と紐づいており、staticmethod()は以下の構文で使用します。
クラス名.クラスメソッド名 = staticmethod(クラス名.メソッド名)
staticmethod()の引数には、スタティックメソッドに変換したいメソッドを指定します。指定したメソッドがスタティックメソッドに変換されて、その戻り値をクラスのアトリビュート(属性)に追加すればOKです。@classmethodと同じく、変換前と変換後のメソッド名は同じで構いません。staticmethod()の使い方の詳細は、以下のサンプルコードのとおりです。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # メソッドを定義する # 引数として整数値を取る def function(value): print("スタティックメソッドが呼び出されました:" + str(value)) # main()|プログラムのエントリーポイント if __name__ == "__main__": # 組み込み関数「staticmethod()」で、Sample.function()をスタティックメソッドに変換する # 変換したメソッドを「function()」という名称で、Sampleクラスのアトリビュート(属性)に追加する Sample.function = staticmethod(Sample.function) # Sampleクラスのスタティックメソッド「Sample.function()」を呼び出す Sample.function(100) // 実行結果
このように、staticmethod()を使用するとSample.function()をスタティックメソッドに変換できます。クラスメソッドとは異なり、固定の引数を定義する必要はありません。
staticmethod()を@staticmethodに行えると、以下のようになります。
# クラスを定義する class クラス名: # スタティックメソッドを定義する @staticmethod def メソッド名(引数): 処理内容 # スタティックメソッドを呼び出す クラス名.メソッド名()
以上の例のように、「@staticmethod」を付加してメソッドを定義すると、間接的にstaticmethod()関数が呼び出されて、スタティックメソッドが生成されます。シンプルなソースコードで記載できるシンタックスシュガーなので、ぜひ活用しましょう。
Pythonの「@classmethod」を使いこなすためには、「インスタンス変数」と「クラス変数」の違いを理解しておくことも重要です。
インスタンス変数はインスタンスメソッドと同じく、オブジェクトのインスタンスに属する変数です。インスタンスごとに異なる値を持つことができるため、各インスタンスで異なる値を保持したいときは、インスタンス変数を使うようにしましょう。
インスタンス変数は以下のように、任意のインスタンスオブジェクト内で「self.変数名 = 初期値」という構文で定義します。
# クラスを定義する class クラス名: # メソッドを定義する def メソッド名(self): # インスタンス変数を定義する self.変数名 = 初期値
重要なポイントは、インスタンス変数の定義時や変更時に必ず「self」を付けることです。selfを付け忘れるとインスタンス変数ではなく、単なるローカル変数になってしまうため、ほかのメソッドから値を参照できません。インスタンス変数の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # コンストラクタを定義する def __init__(self): # インスタンス変数を定義する self.instance_variable = 100 # インスタンスメソッドを定義する def instance_method(self): # インスタンス変数の値を書き換える self.instance_variable *= -1 # インスタンス変数を表示する print(self.instance_variable) # main()|プログラムのエントリーポイント if __name__ == "__main__": # Sampleクラスのインスタンスを生成する instance_1 = Sample() instance_2 = Sample() # インスタンスメソッドを呼び出す instance_1.instance_method() instance_2.instance_method() # インスタンス変数を書き換える instance_1.instance_variable = 10000 instance_2.instance_variable = -1000000 # インスタンスメソッドを呼び出す instance_1.instance_method() instance_2.instance_method() // 実行結果
上記のサンプルプログラムでは、コンストラクタでインスタンス変数「self.instance_variable」を定義し、「instance_method()」で書き換えと表示を行っています。
さらに、main()関数でクラス外からインスタンス変数にアクセスし、中身を書き換えていることもポイントです。実行結果からも分かるように、複数のインスタンスを生成したとしても、それぞれのインスタンス変数は固有の値となります。
クラス変数は、クラス自体に属する変数であり、すべてのインスタンスで共通の値となります。インスタンス変数がインスタンスの数だけ存在するのに対し、クラス変数はただ1つしか存在しません。
そのため、すべてのインスタンスで同じ値を参照できるようにしたいときは、クラス変数を利用すると便利です。クラス変数は以下のように、クラス定義の直下で「変数名 = 初期値」という構文で定義します。
# クラスを定義する class クラス名: # クラス変数を定義する 変数名 = 初期値
重要なポイントは、クラス変数はメソッドの内部で定義しないことです。メソッド内部で「self」を付けて定義するとインスタンス変数になり、selfを付けないとローカル変数になってしまいます。
上記の例のように、メソッドの外部で定義することで、クラス変数として設定できます。クラス変数の使い方をサンプルコードで確認しましょう。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # クラス変数を定義する class_variable = 100 # main()|プログラムのエントリーポイント if __name__ == "__main__": # クラス変数の値を表示する print(Sample.class_variable) # クラス変数の値を変更する Sample.class_variable = -1 # クラス変数の値を表示する print(Sample.class_variable) // 実行結果
上記のサンプルプログラムでは、クラス変数「class_variable」をクラス内で定義し、main()でクラス変数の表示や変更を行っています。このように、クラス変数はクラスメソッドと同じく、インスタンスを生成することなくアクセスできます。インスタンスに紐づいていないからこそ、クラス変数は外部からでも扱うことが可能です。
Pythonのインスタンスメソッドは、インスタンス変数にもクラス変数にもアクセスできます。なぜなら、クラス変数はクラスに属しており、インスタンスメソッドもクラスの一部だからです。詳細については、以下のサンプルコードで確認しましょう。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # クラス変数を定義する class_variable = 1 # コンストラクタを定義する def __init__(self): # インスタンス変数を定義する self.instance_variable = 100 # インスタンスメソッドを定義する def instance_method(self): # インスタンス変数を表示する print(self.instance_variable) # クラス変数を表示する print(Sample.class_variable) # インスタンス変数の値を書き換える self.instance_variable *= -1 # クラス変数の値を書き換える Sample.class_variable *= -1 # インスタンス変数を表示する print(self.instance_variable) # クラス変数を表示する print(Sample.class_variable) # main()|プログラムのエントリーポイント if __name__ == "__main__": # Sampleクラスのインスタンスを生成する instance = Sample() # インスタンスメソッドを呼び出す instance.instance_method() // 実行結果
上記のサンプルプログラムでは、まずSampleクラスにおいて、インスタンス変数「self.instance_variable」とクラス変数「Sample.class_variable」を定義しています。インスタンスメソッド「instance_method」から、両者の変数へのアクセスを試みていますが、問題なく変更や表示ができることが理解できるでしょう。
Pythonのクラスメソッドは、クラス変数のみにアクセス可能で、インスタンス変数は操作できません。なぜなら、インスタンス変数はインスタンスに紐づいているため、クラスそのものに属するクラスメソッドはアクセスする手段を持たないからです。
実際に、インスタンスメソッドは第1引数に自身のインスタンス「self」を取りますが、クラスメソッドの引数はクラスオブジェクトの「cls」です。詳細については、以下のサンプルコードで確認しましょう。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # クラス変数を定義する class_variable = 1 # コンストラクタを定義する def __init__(self): # インスタンス変数を定義する self.instance_variable = 100 # クラスメソッドを定義する @classmethod def class_method(cls): # クラス変数を変更する cls.class_variable = 100 # クラス変数を表示する print(cls.class_variable) # インスタンス変数を表示する print(cls.instance_variable) # main()|プログラムのエントリーポイント if __name__ == "__main__": # クラスメソッドにアクセスする Sample.class_method() // 実行結果
上記のサンプルプログラムでは、先ほどと同様にインスタンス変数「self.instance_variable」とクラス変数「Sample.class_variable」を定義しています。クラスメソッド「class_method」から両者にアクセスしようとしたところ、「cls.instance_variable」の部分でエラーが出ました。
エラーメッセージの意味は、「Sampleクラスはinstance_variableを持たない」というものです。instance_variableはインスタンス変数なので、クラスオブジェクトの「cls」からはアクセスできないということです。instance_variableを操作するためには、インスタンスメソッドからアクセスする必要があります。
Pythonのメソッドで悩みがちなポイントが、「インスタンスメソッド」と「クラスメソッド」の違いです。そこで本章では、それぞれのポイントと向いている用途について解説したうえで、分かりやすい使い分けのサンプルプログラムを紹介します。
インスタンスメソッドには、「インスタンスを介して呼び出す」という特徴があります。重要なポイントは、インスタンス変数にアクセスできることです。
そのため、各インスタンスごとに挙動を制御したい場合は、インスタンスメソッドを使用しましょう。例えば、インスタンスメソッドは以下のように、個別にデータを設定・管理する場合に向いています。
// サンプルプログラム # coding: UTF-8 # randomライブラリをインポートする import random # Carクラス|自動車の動作を制御するクラス class Car: # コンストラクタ def __init__(self, name, mileage, speed): # インスタンス変数を設定する self.name = name # 車名 self.mileage = mileage # 走行距離 self.speed = speed # 速度 # 自動車を走行させる def run(self): # 走行距離に速度を加算する self.mileage += self.speed # 自動車の情報を表示する def print(self): print(f"{self.name}:走行距離{self.mileage}km") # main()|プログラムのエントリーポイント if __name__ == "__main__": # 自動車の台数を設定する num_cars = 10 # 走行回数を設定する num_runs = 100 # インスタンスを格納するリストを用意する cars = [] # 自動車インスタンスを生成する for i in range(num_cars): cars.append(Car(f"自動車{i}", 10000.0 * random.random(), 100.0 * random.random())) # すべての自動車を走行させる for i in range(num_runs): for j in range(num_cars): cars[j].run() # すべての自動車インスタンスの情報を表示する for i in range(num_cars): cars[i].print() // 実行結果
上記のサンプルプログラムは、自動車クラスを定義して、複数のインスタンスの生成と処理を行うものです。各インスタンスは固有の初期値を持ち、いずれも異なる動作を行います。それぞれのインスタンスが持つ変数は固有のものであり、ほかのインスタンスの影響を受けません。
そのため、インスタンスごとに個別の情報を管理したり、振る舞いを設定したりしたい場合は、インスタンスメソッドを使用しましょう。多くのケースでは、インスタンスメソッドが向いているといえます。
クラスメソッドには、「インスタンスに紐づいていない」という特徴があります。重要なポイントは、インスタンス変数にアクセスできず、クラス変数にしかアクセスできないことです。
そのため、すべてのインスタンスで共通の処理を行う必要があり、なおかつインスタンス変数にアクセスしない場面で、クラスメソッドを使用しましょう。例えば、クラスメソッドは以下のように、インスタンス数の管理を行う場合などに役立ちます。
// サンプルプログラム # coding: UTF-8 # randomライブラリをインポートする import random # Sampleクラス class Sample: # クラス変数を定義する num_objects = 0 # 生成済みインスタンス数 # コンストラクタ def __init__(self): # 生成済みインスタンス数を増やす Sample.num_objects += 1 # デストラクタ def __del__(self): # 生成済みインスタンス数を減らす Sample.num_objects -= 1 # 生成済みインスタンス数を表示する @classmethod def print(cls): print(f"インスタンス数:{cls.num_objects}") # main()|プログラムのエントリーポイント if __name__ == "__main__": # 最初のインスタンス数を設定する num = 100 # ループ回数を設定する count = 10000 # インスタンスを格納するリストを用意する cars = [] # インスタンスを生成する for i in range(num): cars.append(Sample()) # すべての自動車を走行させる for i in range(count): # 乱数が偶数であればインスタンスを追加、奇数なら削除する if random.randint(0, 100) % 2 == 0: cars.append(Sample()) else: if len(cars) > 0: cars.pop() # インスタンス数を表示する Sample.print() // 実行結果
上記のサンプルプログラムでは、Sampleクラスに「num_objects」というクラス変数を定義しています。そのうえで、インスタンスが生成されたときにコンストラクタで値を増やし、削除時にデストラクタで減らしています。
num_objectsをクラス変数にすることで、すべてのインスタンスから同じ変数にアクセスすることが可能です。これをインスタンス変数にすると、インスタンス固有の値になるため、全体の数を把握できません。
最終的なインスタンス数を表示するための「print()メソッド」は、クラスメソッドで定義しています。これにより、特定のインスタンスを経由する必要なく、クラス外部からアクセスできます。ただし、コンストラクタやデストラクタで誤って「self.num_objects」と記載すると、クラス変数ではなくインスタンス変数になってしまうので注意が必要です。
インスタンスメソッドとクラスメソッドは、同じクラス内で使い分けるのが基本です。前述したように、個別のインスタンスを制御する場合はインスタンスメソッド、クラス全体の共通処理はクラスメソッドで行います。学校の生徒情報を管理するサンプルプログラムを例として、それぞれを使い分ける方法を見ていきましょう。
// サンプルプログラム # coding: UTF-8 # Classクラス|クラスの生徒情報を管理する class Class: # クラス変数を定義する num_total_students = 0 # 学校全体の生徒数 num_class_students = 0 # クラス内の生徒数 students = [] # 全校生徒の情報 # コンストラクタを定義する def __init__(self, grade, group, teacher): # クラス情報を設定する self.grade = grade # 学年 self.group = group # クラス self.teacher = teacher # 担当教員 # クラスの生徒リストを準備する # 生徒名を文字列形式で格納する self.students = [] # クラス内の生徒数をリセットする Class.num_class_students = 0 # 生徒情報を登録する def register(self, name): # クラスの生徒リストに追加する self.students.append(name) # 全校の生徒リストに追加する Class.students.append(name) # 生徒数を増やす Class.num_total_students += 1 Class.num_class_students += 1 # 生徒情報を表示する def print_students(self): print(self.students) # 学校全体の生徒数を表示するクラスメソッド @classmethod def print_total_students(cls): print(f"学校全体の生徒数:{cls.num_total_students}") # クラス内の生徒数を表示するクラスメソッド @classmethod def print_class_students(cls): print(f"クラス内の生徒数:{cls.num_class_students}") # 学校全体の生徒情報を表示するクラスメソッド @classmethod def print_students(cls): print(cls.students) # main()|プログラムのエントリーポイント if __name__ == "__main__": # 1つ目のクラスインスタンスを生成する class_01 = Class(1, 1, "教員A") # クラスに生徒を追加する class_01.register("生徒1A") class_01.register("生徒1B") class_01.register("生徒1C") class_01.register("生徒1D") class_01.register("生徒1E") # 生徒情報を表示する class_01.print_students() # クラス内の生徒数を表示する Class.print_class_students() # 学校全体の生徒数を表示する Class.print_total_students() # 2つ目のクラスインスタンスを生成する class_02 = Class(1, 1, "教員B") # クラスに生徒を追加する class_02.register("生徒2A") class_02.register("生徒2B") class_02.register("生徒2C") # 生徒情報を表示する class_02.print_students() # クラス内の生徒数を表示する Class.print_class_students() # 学校全体の生徒数を表示する Class.print_total_students() # 3つ目のクラスインスタンスを生成する class_03 = Class(1, 1, "教員C") # クラスに生徒を追加する class_03.register("生徒2A") class_03.register("生徒2B") class_03.register("生徒2C") class_03.register("生徒2D") class_03.register("生徒2E") class_03.register("生徒2F") class_03.register("生徒2G") # 生徒情報を表示する class_03.print_students() # クラス内の生徒数を表示する Class.print_class_students() # 学校全体の生徒数を表示する Class.print_total_students() # 学校全体の生徒情報を表示する Class.print_students() // 実行結果
上記のサンプルプログラムでは、学校のクラスを管理するための「Class」というクラスを定義しています。クラス内部には、全校とクラスの生徒数を管理する2つのクラス変数と、全校生徒の氏名を登録するためのクラス変数があります。
インスタンスメソッドの「register()」では、新しい生徒情報を登録し、その際に生徒数「Class.num_total_students」と「Class.num_class_students」を増やすことがポイントです。ただし、新しいクラスを作成するときは、クラス内の生徒数をリセットします。
このようにすることで、各インスタンスでクラス情報を管理しながら、すべてのインスタンスに共通する全体の生徒数も把握できます。こうした使い分けは、ほかの多くの事例でも役立つため、ぜひやり方を覚えておきましょう。
クラスメソッドについて知っておきたいポイントとして、以下の4つを解説します。
クラスメソッドは基本的に「クラス名」を指定して呼び出しますが、「インスタンス経由」でもアクセスできます。
// サンプルプログラム # coding: UTF-8 # Sampleクラスを定義する class Sample: # クラス変数を定義する class_variable = 100 # クラスメソッドを定義する @classmethod def class_method(cls): # クラス変数を表示する print(cls.class_variable) # main()|プログラムのエントリーポイント if __name__ == "__main__": # クラスメソッドにアクセスする Sample.class_method() # インスタンス経由でクラスメソッドにアクセスする instance = Sample() instance.class_method() // 実行結果
上記のサンプルプログラムでは、通常どおりクラスを指定してクラスメソッドにアクセスしたあとに、インスタンスから呼び出しています。インスタンスから呼び出す場合も機能はまったく変わらないので、必要に応じて使い分けるといいでしょう。ただし、「クラスメソッドへのアクセスであること」を明確化したい場合は、クラス名を指定するのがおすすめです。
クラスメソッドは子クラスに継承することもできます。継承されたクラスメソッドは、子クラスから呼び出せます。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム # coding: UTF-8 # Parentクラス(親クラス)を定義する class Parent: # クラス変数を定義する class_variable = 0 # クラスメソッドを定義する @classmethod def class_method(cls): # クラス変数を表示する print(cls.class_variable) # クラス変数を変更する cls.class_variable = 1 # クラス変数を表示する print(cls.class_variable) # Childクラス(子クラス)を定義する # Parent(親クラス)を継承する class Child(Parent): # 子クラス内では処理を行わない pass # main()|プログラムのエントリーポイント if __name__ == "__main__": # 子クラスのクラスメソッドにアクセスする Child.class_method() // 実行結果
通常のメソッドと同じように、クラスメソッドは自由に継承できるので便利です。子クラスのインスタンスから、親クラスのクラスメソッドにアクセスすることできます。また、クラスメソッドのオーバーライドについても、以下のように通常のメソッドと同様に行えます。
// サンプルプログラム # coding: UTF-8 # Parentクラス(親クラス)を定義する class Parent: # クラス変数を定義する class_variable = 0 # クラスメソッドを定義する @classmethod def class_method(cls): # クラス変数を表示する print(cls.class_variable) # クラス変数を変更する cls.class_variable = 1 # クラス変数を表示する print(cls.class_variable) # Childクラス(子クラス)を定義する # Parent(親クラス)を継承する class Child(Parent): # クラスメソッドを定義する # 子クラスのクラスメソッドをオーバーライドする @classmethod def class_method(cls): # クラス変数を表示する print(cls.class_variable) # クラス変数を変更する cls.class_variable = 100 # クラス変数を表示する print(cls.class_variable) # main()|プログラムのエントリーポイント if __name__ == "__main__": # 子クラスのクラスメソッドにアクセスする Child.class_method() // 実行結果
子クラスで「class_method()」をオーバーライドしているため、親クラスから呼び出した場合とは異なる処理を実現できます。
クラスメソッドは「ファクトリーメソッド」の実装にも役立ちます。ファクトリーメソッドとは、オブジェクトの生成を担当するメソッドのことで、ファクトリーメソッドを活用した設計を「ファクトリーパターン」と呼びます。単純なパターンとして、以下のサンプルコードを確認してみましょう。
// サンプルプログラム # coding: UTF-8 # 「datetime」ライブラリから「date」モジュールをインポートする from datetime import date # Personクラス|個人データを管理する class Person: # コンストラクタを定義する def __init__(self, name, age): # 個人データを設定する self.name = name self.age = age # set_from_birth_year()|誕生年から個人データを登録する @classmethod def set_from_birth_year(cls, name, birth_year): # 「date.today().year」で現在の西暦年を取得する return cls(name, date.today().year - birth_year) # print()|個人データを表示する def print(self): print(f"{self.name}は{self.age}歳です") # main()|プログラムのエントリーポイント if __name__ == "__main__": # 1人目の個人情報を登録してデータを設定する # こちらが通常のインスタンス生成方法 person_1 = Person("佐藤 太郎", 25) person_1.print() # 2人目の個人情報を登録してデータを設定する # こちらは「ファクトリーメソッド」によるインスタンス生成方法 person_2 = Person.set_from_birth_year("田中 次郎", 1980) person_2.print() // 実行結果
上記のサンプルプログラムは、氏名と年齢といった個人データを管理するものです。コンストラクタでは、氏名と年齢の2つの情報を引数とします。
一方で、年齢ではなく「誕生年」を入力し、自動的に年齢を算出させたいこともあるでしょう。しかし、引数の型が同じコンストラクタを2つ定義すると、不正な動作につながります。そこで役立つのが、クラスメソッド(@classmethod)を活用したファクトリーメソッドです。
set_from_birth_year()をクラスメソッドとして定義し、内部で現在の西暦年度から誕生年を差し引いて年齢を設定します。重要なポイントは、メソッド内でcls変数を使用してコンストラクタを呼び出し、生成したインスタンスを戻り値として返していることです。このようにすることで、Person.set_from_birth_year()を呼び出すと、インスタンスを生成できるようになります。
なお、Person.set_from_birth_year()の内容をクラスメソッドではなく、以下のサンプルコードのように、グローバル空間に関数として定義する方法も考えられるでしょう。
// サンプルプログラム # coding: UTF-8 # 「datetime」ライブラリから「date」モジュールをインポートする from datetime import date # Personクラス|個人データを管理する class Person: # コンストラクタを定義する def __init__(self, name, age): # 個人データを設定する self.name = name self.age = age # print()|個人データを表示する def print(self): print(f"{self.name}は{self.age}歳です") # set_from_birth_year()|誕生年と現在の西暦年からPersonインスタンスを生成する def set_from_birth_year(name, birth_year): # 「date.today().year」で現在の西暦年を取得する return Person(name, date.today().year - birth_year) # main()|プログラムのエントリーポイント if __name__ == "__main__": # 1人目の個人情報を登録してデータを設定する # こちらが通常のインスタンス生成方法 person_1 = Person("佐藤 太郎", 25) person_1.print() # 2人目の個人情報を登録してデータを設定する # こちらは「ファクトリーメソッド」によるインスタンス生成方法 person_2 = set_from_birth_year("田中 次郎", 1980) person_2.print() // 実行結果
機能的にはクラスメソッド(@classmethod)を使用した場合と変わりません。しかし、set_from_birth_year()がクラス内に含まれていないため、ライブラリ化するときにimportなどの面で不便になります。そのため、あるデータ型に関連する関数については、できるだけクラスメソッドとしてクラスに含めるほうがいいでしょう。
クラスメソッド(@classmethod)を活用したファクトリーメソッドは、より複雑なプログラム開発にも対応できます。クラスの継承が多いプログラム設計の場合は、インスタンス生成の際に必要に応じたデータ型を指定する必要があるため、コーディングが煩雑になるでしょう。
そこで、以下のサンプルコードのようにファクトリーメソッドを活用して、インスタンス生成をクラスメソッドに一任することで簡潔なコーディングが可能となります。
// サンプルプログラム # coding: UTF-8 # 「abc」ライブラリの「ABC」「abstractmethod」モジュールをインポートする from abc import ABC, abstractmethod # Factoryクラス|Productインスタンスの作成と管理を担うファクトリー class Factory: # 作成したProductインスタンスのリスト product_list = [] # create()|Productインスタンスを作成する @classmethod def create(cls, type, name): # 作成したProductインスタンスを格納する変数 product = None # Carクラスが指定された場合 if type == "Car": product = Car(name) # Truckクラスが指定された場合 elif type == "Truck": product = Truck(name) # Motorcycleクラスが指定された場合 elif type == "Motorcycle": product = Motorcycle(name) # 上記以外のクラスが指定された場合 else: print("不明なオブジェクトタイプが指定されました") # ヌルオブジェクトでなければリストに追加する if product != None: Factory.product_list.append(product) # インスタンスを返す return product # print()|生成したProductリストの情報を表示する # クラスメソッドなのでインスタンスを介さずに外部からアクセスできる @classmethod def print(cls): # 生成したインスタンス数を表示する print(f"生産した製品数:{len(cls.product_list)}") # すべてのインスタンス情報を表示する for x in cls.product_list: x.print() # Product|Factoryクラスでインスタンスを作成する # 抽象クラスにするために「ABCクラス」を継承する class Product(ABC): # クラス変数で製品の種類を設定する product_type = "" # コンストラクタを定義する def __init__(self, name): # 個人データを設定する self.name = name # print()|製品の情報を表示する # 抽象メソッドにするために「@abstractmethod」デコレータを付与する @abstractmethod def print(self): pass # Carクラス|乗用車クラス # Productクラスを継承する class Car(Product): # クラス変数で製品の種類を設定する product_type = "乗用車" # コンストラクタを定義する def __init__(self, name): # 親クラスのインスタンスを呼び出す super().__init__(name) # print()|乗用車の情報を表示する # 基底クラスで抽象メソッドとなっているため実装必須 def print(self): print(f"「{Car.product_type}」の「{self.name}」") # Truckクラス|トラッククラス # Productクラスを継承する class Truck(Product): # クラス変数で製品の種類を設定する product_type = "トラック" # コンストラクタを定義する def __init__(self, name): # 親クラスのインスタンスを呼び出す super().__init__(name) # print()|トラックの情報を表示する # 基底クラスで抽象メソッドとなっているため実装必須 def print(self): print(f"「{Truck.product_type}」の「{self.name}」") # Motorcycleクラス|オートバイクラス # Productクラスを継承する class Motorcycle(Product): # クラス変数で製品の種類を設定する product_type = "オートバイ" # コンストラクタを定義する def __init__(self, name): # 親クラスのインスタンスを呼び出す super().__init__(name) # print()|オートバイの情報を表示する # 基底クラスで抽象メソッドとなっているため実装必須 def print(self): print(f"「{Motorcycle.product_type}」の「{self.name}」") # main()|プログラムのエントリーポイント if __name__ == "__main__": # 乗用車のインスタンスを生成する car = Factory.create("Car", "スポーツカー") # トラックのインスタンスを生成する truck = Factory.create("Truck", "大型トラック") # オートバイのインスタンスを生成する motorcycle = Factory.create("Motorcycle", "ロードバイク") # 生成した製品インスタンスの情報を表示する Factory.print() // 実行結果
上記のサンプルプログラムでは、「Productクラス」から派生したさまざまなクラスを、「Factoryクラス」のcreate()メソッドで生成していることがポイントです。create()内部では、引数で指定されたクラス型のインスタンスを生成し、呼び出し元に返します。さらに、生成したオブジェクトを一括管理するために、クラス変数「product_list」に格納することも重要です。
一方で、Productクラスは「ABCクラス」を継承した抽象クラスであり、「Car」「Truck」「Motorcycle」という3つの派生クラスが存在します。クラスメソッドであるFactory.create()を外部から呼び出すことで、これらのクラスを適切に生成することが可能となります。なお、組み込み関数の「eval()」を利用すると、create()の実装がより簡潔になるので便利です。
# create()|Productインスタンスを作成する @classmethod def create(cls, type, name): # eval()関数を利用して、文字列からクラスのインスタンスを生成する product = eval(type)(name) # 生成したインスタンスをリストに追加する Factory.product_list.append(product) # インスタンスを返す return product
eval関数は、第1引数を式として評価します。そのため、文字列として受け取った「type」を、クラスのデータ型として解釈して自動的にインスタンスを生成可能です。
なお、存在しないクラス名を指定した場合は、以下のようなエラー画面が表示されます。
Pythonのメソッドを定義する際に、@classmethodデコレータを付与すると、インスタンスに属さないクラスメソッドを作成できます。
クラスメソッドは、すべてのインスタンスで共通の値を参照できることや、インスタンスなしでもアクセスできることなどから、上手く使いこなすとコーディングの効率化に便利です。今回ご紹介した知識やテクニックを活用して、ぜひPythonの@classmethodでクラスメソッドを作成してみましょう。
2024.06.17
子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介
#プログラミングスクール
2022.01.06
【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説
#プログラミングスクール
2024.01.26
【未経験でも転職可】30代におすすめプログラミングスクール8選!
#プログラミングスクール
2024.01.26
初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説
#JAVA
2024.01.26
忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説
#プログラミングスクール
2022.01.06
【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介
#プログラミングスクール
2024.01.26
【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説
#プログラミングスクール
2024.01.26
【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説
#プログラミングスクール