初心者のための@classmethodと@staticmethodの意味?

誰かpythonの@classmethod@staticmethodの意味を説明してくれませんか?その違いと意味を知りたいのです。

私が理解している限りでは、@classmethodはクラスに、それがサブクラスに継承されるべきメソッドであることを伝える、とか。しかし、それにはどんな意味があるのでしょうか?classmethod@staticmethodなどの@` 定義を追加することなく、クラスのメソッドを定義すればいいのでは?

tl;dr: when should I use them, why should I use them, how should I use them?

私はC++をかなり使いこなしているので、より高度なプログラミング・コンセプトを使うことは問題ないでしょう。可能であれば、対応するC++の例を気軽に教えてください。

ソリューション

classmethodstaticmethodは非常によく似ていますが、両方のエンティティの使用法にはわずかな違いがあります。classmethodは最初のパラメータとしてクラスオブジェクトへの参照を持たなければなりませんが、staticmethodはパラメータを一切持つことができません。


class Date(object):

    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day 
解説 (2)

Rostyslav Dzinko'さんの回答はとても適切です。追加のコンストラクタを作成する際に、@staticmethodではなく@classmethodを選択すべきもう1つの理由を紹介しようと思います。

上の例では、Rostyslav 氏はファクトリーとして @classmethodfrom_string を使用して、他の方法では受け入れられないパラメーターから Date オブジェクトを作成しています。以下のコードに示すように、@staticmethod でも同様のことができます。

class Date:
  def __init__(self, month, day, year):
    self.month = month
    self.day   = day
    self.year  = year

  def display(self):
    return "{0}-{1}-{2}".format(self.month, self.day, self.year)

  @staticmethod
  def millenium(month, day):
    return Date(month, day, 2000)

new_year = Date(1, 1, 2013)               # Creates a new Date object
millenium_new_year = Date.millenium(1, 1) # also creates a Date object. 

# Proof:
new_year.display()           # "1-1-2013"
millenium_new_year.display() # "1-1-2000"

isinstance(new_year, Date) # True
isinstance(millenium_new_year, Date) # True

このように、new_yearmillenium_new_yearはどちらもDateクラスのインスタンスです。

しかし、よく観察してみると、ファクトリー処理では何があってもDateオブジェクトを生成するようにハードコーディングされています。つまり、Dateクラスがサブクラス化されても、サブクラスは(サブクラスのプロパティを持たない)プレーンなDateオブジェクトを作成するということになります。以下の例を見てください。

class DateTime(Date):
  def display(self):
      return "{0}-{1}-{2} - 00:00:00PM".format(self.month, self.day, self.year)

datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # False

datetime1.display() # returns "10-10-1990 - 00:00:00PM"
datetime2.display() # returns "10-10-2000" because it's not a DateTime object but a Date object. Check the implementation of the millenium method on the Date class for more details.

datetime2DateTimeのインスタンスではありませんか?となっています。それは、@staticmethod`というデコレータが使われているからです。

ほとんどの場合、これは望ましくありません。もし、呼び出したクラスを認識するFactoryメソッドが欲しいのであれば、@classmethodが必要になります。

Date.millenium`を次のように書き換えます(上のコードで変更になるのはこの部分だけです)。

@classmethod
def millenium(cls, month, day):
    return cls(month, day, 2000)

と書き換えることで、classがハードコードされているのではなく、学習されていることがわかります。clsには任意のサブクラスを指定できます。結果として得られるobjectは当然ながらcls` のインスタンスとなります。
これをテストしてみましょう。

datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # True

datetime1.display() # "10-10-1990 - 00:00:00PM"
datetime2.display() # "10-10-2000 - 00:00:00PM"

理由はご存知の通り、@staticmethodの代わりに@classmethodが使われたからです。

解説 (2)

@classmethod`とは:このメソッドが呼ばれるとき、(通常のメソッドのように)そのクラスのインスタンスではなく、クラスを第一引数として渡します。つまり、特定のインスタンスではなく、クラスとそのプロパティをそのメソッド内で使用することができます。

staticmethod`とは:このメソッドが呼ばれたときに、(通常のメソッドのように)クラスのインスタンスを渡さないということです。つまり、クラスの中に関数を置くことはできても、そのクラスのインスタンスにアクセスすることはできないということです(これは、メソッドがインスタンスを使用しない場合に便利です)。

解説 (0)