誰か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++の例を気軽に教えてください。
classmethodと
staticmethodは非常によく似ていますが、両方のエンティティの使用法にはわずかな違いがあります。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 <= 31 and month <= 12 and year <= 3999
date2 = Date.from_string('11-09-2012')
is_date = Date.is_date_valid('11-09-2012')
日付情報を扱うクラスの例を考えてみましょう(これが定型文になります)。
class Date(object):
def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year
このクラスは明らかに、特定の日付に関する情報を格納するために使用できます(タイムゾーンの情報は含まず、すべての日付がUTCで表示されると仮定します)。
これは典型的な instancemethod
として引数を受け取り、新しく作成されたインスタンスへの参照を保持する最初の非オプション引数 (self
) を持ちます。
クラスメソッド.
クラスメソッド`を使ってうまく処理できるタスクがいくつかあります。
*ここでは、外部ソースからの日付情報を 'dd-mm-yyyy'形式の文字列としてエンコードした、たくさんの Date
クラスインスタンスを作成したいと仮定します。これを、プロジェクトのソースコードの様々な場所で行う必要があるとします。
そこで、ここでやらなければならないことは
1.文字列を解析して、日、月、年を3つの整数変数として受け取るか、その変数からなる3項目のタプルを受け取る。
2.2. これらの値を初期化コールに渡してDate
をインスタンス化する。
これは次のようになります。
day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)
この目的のために、C++ではオーバーロードでこのような機能を実装できますが、Pythonにはこのオーバーロードがありません。代わりに classmethod
を使うことができます。では、もう一つの"constructor"を作ってみましょう。
@classmethod
def from_string(cls, date_as_string):
day, month, year = map(int, date_as_string.split('-'))
date1 = cls(day, month, year)
return date1
date2 = Date.from_string('11-09-2012')
上記の実装をもっと注意深く見て、どんな利点があるのか検討してみましょう。
1.1. 日付文字列の解析を一か所にまとめて実装したので、再利用が可能です。
2.2. カプセル化がうまく機能している(文字列解析を他の場所で単一の関数として実装できると考えた場合、この解決策はOOPパラダイムにはるかに適しています)。
3. cls
は、クラスのインスタンスではなく、クラスそのものを保持するオブジェクトです。Dateクラスを継承した場合、すべての子クラスにfrom_string
が定義されることになるからです。
スタティックなメソッド。
では、staticmethod
はどうでしょうか。classmethod`とよく似ていますが、(クラスメソッドやインスタンスメソッドのように)義務的なパラメータは取りません。
次の使用例を見てみましょう。
日付の文字列があり、それを何らかの方法で検証したいとします。このタスクも、これまで使ってきたDate
クラスに論理的に結びついていますが、そのインスタンス化は必要ありません。
ここで、staticmethod
が役に立ちます。では、次のコードを見てみましょう。
@staticmethod
def is_date_valid(date_as_string):
day, month, year = map(int, date_as_string.split('-'))
return day <= 31 and month <= 12 and year <= 3999
# usage:
is_date = Date.is_date_valid('11-09-2012')
つまり、staticmethod
の使い方からわかるように、クラスが何であるかにはアクセスできません。基本的には単なる関数であり、構文的にはメソッドのように呼び出されますが、classmethod がアクセスできるのに対して、オブジェクトとその内部(フィールドや別のメソッド)にはアクセスできません。
Rostyslav Dzinko'さんの回答はとても適切です。追加のコンストラクタを作成する際に、@staticmethod
ではなく@classmethod
を選択すべきもう1つの理由を紹介しようと思います。
上の例では、Rostyslav 氏はファクトリーとして @classmethod
の from_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_year
とmillenium_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.
datetime2は
DateTimeのインスタンスではありませんか?となっています。それは、
@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
が使われたからです。
@classmethod`とは:このメソッドが呼ばれるとき、(通常のメソッドのように)そのクラスのインスタンスではなく、クラスを第一引数として渡します。つまり、特定のインスタンスではなく、クラスとそのプロパティをそのメソッド内で使用することができます。
staticmethod`とは:このメソッドが呼ばれたときに、(通常のメソッドのように)クラスのインスタンスを渡さないということです。つまり、クラスの中に関数を置くことはできても、そのクラスのインスタンスにアクセスすることはできないということです(これは、メソッドがインスタンスを使用しない場合に便利です)。