Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

【Python入門】クラスメソッド(@classmethod)の基本の解説

Pythonのクラスメソッド「@classmethod」について、その基本的な考え方と使い方を解説

今回はPython初心者向け文法解説のうち、「@classmethod」(クラスメソッド)について、その基本的な考え方と使い方を解説します。

前提知識:Pythonのデコレータについて

まず、@classmethodはデコレータとして使いますので、それを理解するためにPythonのデコレータについての基礎知識が必要となります。デコレータについてはこちらの記事で解説していますので、最初に読んでおいてください。

公式ドキュメント

Python公式ドキュメント:@classmethod

@classmethodとは?

メソッドとクラスメソッドという用語の違いについて

クラスメソッドはまるで「メソッド」または「クラス『の』メソッド」のように思われる用語ですが、実は、「クラスメソッド」と、「メソッド(クラスのメソッド)」とは全く違う意味です。

  • メソッドまたはクラスのメソッド:クラス内部で定義された関数
  • クラスメソッド:@classmethodのこと

このように似た言葉ですが、意味するところが異なるので注意したいところです。

デコーレータの1種

さてクラスメソッド(@classmethod)は、クラス内部のメソッドに対して使用されるデコレーターの一種です。おなじように、クラス内部のメソッドに対して使用されるデコレータには@staticmethodや@propertyというものもあります。このうち@staticmethodについては以下の記事で解説しています。

特徴

@classmethodの最大の特徴は、それを実行すると自動的に第一引数としてクラスが入るという点です。

使い方

ひとまず先に使い方を見ておきましょう。

class C:
  (略)

    @classmethod
    def f(cls, (略)): 
       (略)


# クラスそのものから呼び出す方法
C.f()
# クラスインスタンスから呼び出す方法
C().f()

上述のように@classmethodはデコレータなので関数の直前につけます。そして上のコードのように、呼び出す方法が2つあり、どちらもかまいません

また上の特徴で書いたように、

    def f(cls, (略)): 

この関数fの第一仮引数clsには、そのクラスであるMyClassそのものが自動的に入ります。

それを確かめるために次のようなコードを書いてみるといいでしょう。

    def f(cls, (略)): 
       print(cls)

こうすれば引数clsの中身がわかります。

さて、上のどちらの呼び出し方にするかは、おそらくプロジェクトのチーム内でのルールで決まることでしょう。個人開発でもどちらにするのかは決めておいたほうが後々になって読み返したときに読みやすいと思います。

では次に実際のコード例から@classmethodの働きを見ていきましょう。

使用例とその解説

@classmethodを使わない基本形

まずはクラスメソッド(@classmethod)を使わない普通のクラスの場合を見ていきましょう。

class MyClass:
    
    def show_id_of_first_parameter(self):
        print(f'引数selfのID:{id(self)}')

# 組み込み関数id()は、各オブジェクト固有の識別番号を返す
id(MyClass)

id(MyClass())  # id(MyClass)とは異なる値

obj = MyClass()  # インスタンス = クラスインスタンスの形式

id(obj)  #id(MyClass())と同じ値

# 通常のクラスのようにインスタンスからメソッドを呼び出す
obj.show_id_of_first_parameter()  # id(obj)と同じ値

では次にクラスメソッドを使った場合をみていきましょう。コードはほとんど同じです。

@classmethodを使った基本形

class MyClass:
    @classmethod    
    def show_id_of_first_parameter(cls):
        print(f' メソッドshow_id_of_first_parameterの第1引数のID:{id(cls)}')


id(MyClass)

id(MyClass())  # id(MyClass)とは異なる値

obj = MyClass()  # インスタンス = クラスインスタンスの形式

id(obj)  #id(MyClass())と同じ値

# クラスメソッドの呼び出し方(インスタンスを経由しない)
MyClass.show_id_of_first_parameter()  # id(MyClass)の結果と一致

上の2つのコードで、クラスメソッドを使わない通常の場合とそれを使う場合を比較すると、大きな相違点は次の部分です。

コード(メソッドの呼び出し方)実行結果
obj.show_id_of_first_parameter() id(obj)と同じ値
MyClass.show_id_of_first_parameter()id(MyClass)と同じ値

このような違いがあるのは、クラスメソッド(@classmethod)を使う場合は、まさに上の見出し「特徴」のところで書いた点の、「この関数fの第一仮引数clsには、そのクラスであるMyClassそのものが自動的に」入るからです。

ではこのような@classmethodには一体どんなメリットや使い勝手の良さがあるのでしょうか?次の項目でその一例を紹介します。

@classmethodを使うメリットとは?

クラスメソッド(@classmethod)を使うメリットは人によって考え方がいろいろあると思いますが、今回は以下の応用例その1でそのメリットの1つを説明したいと思います。

応用例その1

class Shoes:
    # インスタンス初期化
    def __init__(self, *args, **kwargs):
        self.product_info = [args, kwargs]
        self.product_id = args[0]
        self.price = args[1]
        self.brand = kwargs["brand"]
        self.model_name = kwargs["model_name"]
        
    # 割引価格の処理(今回は何もしない)
    def discount(self):
        pass
    # Nikeのシューズについてのインスタンスの初期化
    @classmethod
    def nike(cls):
        return cls(123, 10000, brand = 'Nike', model_name = 'Air')

    # Pumaのシューズについてのインスタンスの初期化
    @classmethod
    def puma(cls):
        return cls(223, 15000, brand = 'Puma', model_name = 'Classic Suede')
    
    
shoes = Shoes()
shoes.product_info

nike = Shoes.nike()
nike.product_id
nike.price
nike.brand
nike.model_name

puma = Shoes.puma()
puma.product_id
puma.price
puma.brand
puma.model_name

上のコード例では、スニーカーブランドのナイキとプーマについて2つのインスタンスを作っています。

この点クラスメソッドを使わない通場合ですと、

Class Nike:


Class Puma:

というようにそれぞれクラスを作る方法もあるかと思います。

ですがそれぞれの値段、モデル名、ブランド名などの情報を設定するという処理はどちらも共通ですので1つにまとめたいところです。また、

class Shoes:
   def __init__(self, ):

      
nike = Shoes("Nike", 略)
puma = Shoes("Puma", 略)

このようにするのも普通にあると思います。

ですがこのやり方だと、毎回オブジェクトを作る時に実引数(Shoesがとる引数)を書かなければなりません。それが1つか2つだとさほど不便は感じませんが、引数の数が10や20になると毎回書くのも面倒ですし、書き漏らしのミスが出てくるかもしれません。

ですが、上のクラスメソッド(@classmethod)を使ったコードですと、

nike = Shoes.nike()
puma = Shoes.puma()

とするだけで各種の情報を設定されたインスタンスを作ることができます。引数設定のミスも減らせるかもしれませんし、見た目も非常にシンプルです。

また、Pythonの場合は、

  • クラス内部においてインスタ初期化の特殊メソッド__init__()は1つしか定義しない
  • __init__()はあくまでインスタンス初期化のためだけに記述し、その内部で複雑な処理をさせない

という考え方・実装が良いとされていますので__init__()によるインスタンス初期化処理はできるだけシンプルにしたいというがあります。

そこでクラスメソッド(@classmethod)使ったコードを見てもらうと、そのクラスメソッド部分がまさにインスタンス初期化処理を担っていることがわかってもらえると思います。

2つのクラスメソッドに共通する部分だけを__init__()に記述しておき、両クラスメソッドはそれを使ってインスタンスを初期化しているという構造です。

こうすることで__init__()そのものはできるだけシンプルにしておき、具体的な処理はクラスメソッド側に任せるというシステムを作ることができます。

では最後に応用例その2を紹介しておきます。

応用例その2

class Shoes:
    def __init__(self, *args, **kwargs):
        self.id = args[0]
        self.price =  args[1]
        self.brand = kwargs["brand"]
        self.model_name = kwargs["model_name"]

    def discount(self):
        discounted_price = self.price*0.8
        return discounted_price

    # インスタンスの初期化に変更を加える用
    @classmethod
    def modify_initialization(cls, *paras, **kwparas):
        print(f'引数clsの中身は:{cls}, \n引数parasの中身は:{paras}, \n引数kwparasの中身は:{kwparas}')
        modified_instance = cls(*paras, **kwparas)
        return modified_instance
  
    # 最初のセール価格からさらに割引へ  
    @classmethod
    def discount_wrapper(cls):
        def final_discount(method):
            print(method(shoes)*0.5)
            
        return final_discount(cls.discount)


# 最初にNikeのシューズとして登録        
shoes = Shoes(123, 10000, brand= "Nike", model_name = "Air Jordan")
shoes.id
shoes.price
shoes.brand
shoes.model_name
shoes.discount()

# NikeからPumaのシューズへ情報修正
modified_shoes = Shoes.modify_initialization(551, 100, brand = "Puma", model_name = "Classic Suede")

modified_shoes.price
modified_shoes.model_name

# 最初のNikeの価格をさらなる割引価格へ
Shoes.discount_wrapper()

ちょっと不必要にわかりにくいコードかもしれませんが、ポイントは、

    @classmethod
    def discount_wrapper(cls):
        def final_discount(method):
            print(method(shoes)*0.5)
            
        return final_discount(cls.discount)

の部分です。この引数clsに「クラスが自動的に入る」ということによって、この部分のコードの働きが実現されています。(def discout_wrapperそのものもデコレータっぽく書いています)

関連項目:スタティックメソッ(@staticmethod)とプロパティ(@property)について

クラス内部で使うデコレータとして有名なものは、今回解説した@classmethod以外にもスタティックメソッド(静的メソッド:@staticmehtod)とプロパティ(@property)もあります。こちらについても以下の記事で解説をしていますので読んでいただければと思います。

おすすめPython学習法

Pythonも他のプログラミング言語と同じくかなり奥が深く、覚えたい知識も幅広く大量です。また、プログラミング学習でもただ解説された文字を見ているだけでは理解しにくく、また初心者が忙しい日々の中で独学するのはなかなか時間がかかりますし、そのうちモチベーションも下がっていくでしょう。

そこで、できる限り合理的かつ短期間でマスターするために動画学習が効果的です。下の記事ではPython初心者におすすめの、Pythonの基礎を学べる動画を5つ紹介しています。ぜひ参考にしてみてください。