web-dev-qa-db-ja.com

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

誰かが私にPythonで@classmethod@staticmethodの意味を説明してもらえますか?違いと意味を知る必要があります。

私が理解している限りでは、@classmethodはクラスに、それがサブクラスに継承されるべきなのか、あるいは何かなのかを伝えます。しかし、そのポイントは何ですか? @classmethod@staticmethodあるいは@の定義を追加せずにクラスメソッドを定義しないのはなぜですか。

tl; dr: いつ を使うべきか、 なぜ を使うべきか、 how を使うべきか

私はC++をかなり進んでいるので、もっと高度なプログラミング概念を使っても問題にならないはずです。可能であれば、対応するC++の例を教えてください。

1455
user1632861

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 <= 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で提示されると仮定しましょう)。

ここには、Pythonクラスインスタンスの典型的な初期化子である__init__があります。これは典型的なinstancemethodとして引数を受け取り、新しく作成されたインスタンスへの参照を保持する最初の非オプション引数(self)を持ちます。

クラスメソッド

classmethodsを使ってうまくできるタスクがいくつかあります。

'dd-mm-yyyy'という形式の文字列としてエンコードされた外部ソースからの日付情報を持つDateクラスのインスタンスをたくさん作成したいとしましょう。プロジェクトのソースコードのさまざまな場所でこれを実行する必要があるとします。

だからここでやらなければならないことは、

  1. 文字列を解析して、日、月、年を3つの整数変数、またはその変数からなる3項目のタプルとして受け取ります。
  2. これらの値を初期化呼び出しに渡すことによってDateをインスタンス化します。

これは次のようになります。

day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)

この目的のために、C++はそのような機能をオーバーロードで実装することができますが、Pythonはこのオーバーロードを欠いています。代わりにclassmethodを使うことができます。別の " コンストラクタ "を作成しましょう。

    @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. カプセル化はここではうまく機能します(あなたが他の場所で単一の関数として文字列解析を実装することができると思うなら、この解決法は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が行いますが、別の方法)。

2420

Rostyslav Dzinkoの答えはとても適切です。追加のコンストラクタを作成するときに@classmethodより@staticmethodを選択する必要があるというもう1つの理由を強調できると思いました。

上の例では、Rostyslavは@classmethodfrom_stringをFactoryとして使用して、それ以外の方法では受け入れられないパラメータから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クラスのインスタンスです。

しかし、よく観察すると、Factoryプロセスは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

datetime2DateTimeのインスタンスではありませんか? WTF?それは@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"

その理由は、ご存知のとおり、@classmethodの代わりに@staticmethodが使用されたためです。

795
Yaw Boakye

@classmethodは、このメソッドが呼び出されたときに、そのクラスのインスタンスではなく、クラスを最初の引数として渡すことを意味します(通常はメソッドで行います)。つまり、特定のインスタンスではなく、そのメソッド内でクラスとそのプロパティを使用できます。

@staticmethodは、このメソッドが呼び出されたときに、クラスのインスタンスをそれに渡すことがないことを意味します(通常はメソッドで行うように)。つまり、クラス内に関数を入れることはできますが、そのクラスのインスタンスにアクセスすることはできません(これは、メソッドがインスタンスを使用しない場合に役立ちます)。

229
Simeon Visser

それぞれを使うとき

@staticmethod関数はクラス内で定義された関数にすぎません。最初にクラスをインスタンス化せずに呼び出すことができます。その定義は継承によって不変です。

  • Pythonはobjectに対して bound-method をインスタンス化する必要はありません。
  • それはコードの読みやすさを容易にします。 @staticmethod を見ると、このメソッドはオブジェクト自体の状態に依存しないことがわかります。

@classmethod関数はクラスをインスタンス化せずに呼び出すこともできますが、その定義は継承を通じてParentクラスではなくSubクラスに従いますが、サブクラスでオーバーライドできます。これは、@classmethod関数の最初の引数が常にcls (class)でなければならないためです。

  • ファクトリメソッド 、例えばある種の前処理を使用してクラスのインスタンスを作成するために使用されます。
  • 静的メソッドを呼び出す静的メソッド :静的メソッドを複数の静的メソッドに分割する場合は、クラス名をハードコードしないでクラスメソッドを使用する必要があります。

ここ はこのトピックへのリンクです。

69
zangw

@classmethod@staticmethodの意味は?

  • メソッドは、属性としてアクセス可能な、オブジェクトのネームスペース内の関数です。
  • 通常の(すなわちインスタンス)メソッドは、暗黙の最初の引数としてインスタンス(通常はselfと呼びます)を取得します。
  • class メソッドは、暗黙の最初の引数としてクラス(通常はclsと呼びます)を取得します。
  • static メソッドは、(通常の関数のように)暗黙の最初の引数を取りません。

いつ使うべきですか、なぜ使うべきですか、そしてどのように使うべきですか?

必要 どちらのデコレータも必要ありません。しかし、関数への引数の数を最小限に抑えるべきであるという原則(Clean Coderを参照)では、それらはそのために役立ちます。

class Example(object):

    def regular_instance_method(self):
        """A function of an instance has access to every attribute of that 
        instance, including its class (and its attributes.)
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_f(self)

    @classmethod
    def a_class_method(cls):
        """A function of a class has access to every attribute of the class.
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_g(cls)

    @staticmethod
    def a_static_method():
        """A static method has no information about instances or classes
        unless explicitly given. It just lives in the class (and thus its 
        instances') namespace.
        """
        return some_function_h()

インスタンスメソッドとクラスメソッドの両方にとって、少なくとも1つの引数を受け付けないことはTypeErrorですが、その引数の意味を理解していないことはユーザーエラーです。

some_functionを定義します。例:

some_function_h = some_function_g = some_function_f = lambda x=None: x

これでうまくいくでしょう。)

インスタンスとクラスのドット付きルックアップ:

インスタンス上のドット付きルックアップはこの順序で実行されます。

  1. クラス名前空間のデータ記述子(プロパティのように)
  2. インスタンス内のデータ__dict__
  3. クラス名前空間内の非データ記述子(メソッド).

インスタンスのドット付きルックアップは次のように呼び出されます。

instance = Example()
instance.regular_instance_method 

メソッドは呼び出し可能な属性です。

instance.regular_instance_method()

インスタンスメソッド

引数selfは、ドット付きルックアップを介して暗黙的に与えられます。

クラスのインスタンスからインスタンスメソッドにアクセスする必要があります。

>>> instance = Example()
>>> instance.regular_instance_method()
<__main__.Example object at 0x00000000399524E0>

クラスメソッド

引数clsは、ドット付きルックアップを介して暗黙的に与えられます。

このメソッドには、インスタンスまたはクラス(またはサブクラス)を介してアクセスできます。

>>> instance.a_class_method()
<class '__main__.Example'>
>>> Example.a_class_method()
<class '__main__.Example'>

静的メソッド

暗黙のうちに引数はありません。このメソッドは、検索できるという点を除いて、モジュールの名前空間で(たとえば)定義されている関数と同じように機能します。

>>> print(instance.a_static_method())
None

繰り返しますが、いつ使用する必要がありますか。なぜ使用する必要があるのですか。

これらのそれぞれは、それらがメソッドを通過する情報とインスタンスメソッドの間で次第に制限が厳しくなります。

あなたが情報を必要としないときにそれらを使ってください。

これにより、関数やメソッドを推論したりunittestしたりするのが簡単になります。

どちらが推論しやすいですか?

def function(x, y, z): ...

または

def function(y, z): ...

または

def function(z): ...

引数が少ない関数の方が推論が容易です。それらはまたunittestするのがより簡単です。

これらはインスタンス、クラス、そして静的メソッドに似ています。インスタンスがあるときは、そのクラスもあることを忘れないでください。

def an_instance_method(self, arg, kwarg=None):
    cls = type(self)             # Also has the class of instance!
    ...

@classmethod
def a_class_method(cls, arg, kwarg=None):
    ...

@staticmethod
def a_static_method(arg, kwarg=None):
    ...

組み込みの例

これが私のお気に入りの組み込みの例です。

str.maketrans静的メソッドはstringモジュールの関数でしたが、str名前空間からアクセスできるほうがはるかに便利です。

>>> 'abc'.translate(str.maketrans({'a': 'b'}))
'bbc'

dict.fromkeysクラスメソッドは、繰り返し可能なキーからインスタンス化された新しい辞書を返します。

>>> dict.fromkeys('abc')
{'a': None, 'c': None, 'b': None}

サブクラス化すると、クラス情報をクラスメソッドとして取得することがわかります。これは非常に便利です。

>>> class MyDict(dict): pass
>>> type(MyDict.fromkeys('abc'))
<class '__main__.MyDict'> 

私のアドバイス - まとめ

クラスやインスタンスの引数が不要な場合は静的メソッドを使用しますが、関数はオブジェクトの使用に関連しているため、関数がオブジェクトの名前空間にあると便利です。

インスタンス情報を必要としないが、おそらく他のクラスまたは静的メソッドのクラス情報を必要とする場合、またはおそらくそれ自体をコンストラクターとして必要とする場合は、クラスメソッドを使用します。 (サブクラスをここで使用できるように、クラスをハードコードしないでください。)

35
Aaron Hall

どのサブクラスがメソッドを呼び出しているかに基づいてメソッドの動作を変更したい場合は、@classmethodを使用します。呼び出し元クラスへの参照がクラスメソッド内にあることを忘れないでください。

静的を使用している間は、サブクラス間で振る舞いを変えないでください。

例:

class Hero:

  @staticmethod
  def say_hello():
     print("Helllo...")

  @classmethod
  def say_class_hello(cls):
     if(cls.__name__=="HeroSon"):
        print("Hi Kido")
     Elif(cls.__name__=="HeroDaughter"):
        print("Hi Princess")

class HeroSon(Hero):
  def say_son_hello(self):
     print("test  hello")



class HeroDaughter(Hero):
  def say_daughter_hello(self):
     print("test  hello daughter")


testson = HeroSon()

testson.say_class_hello() #Output: "Hi Kido"

testson.say_hello() #Outputs: "Helllo..."

testdaughter = HeroDaughter()

testdaughter.say_class_hello() #Outputs: "Hi Princess"

testdaughter.say_hello() #Outputs: "Helllo..."
34

ちょっとした編集

@staticmethod 呼び出されているオブジェクトを参照せずにクラス内にメソッドを記述する方法。だからselfやclsのような暗黙の引数を渡す必要はありません。それはクラスの外で書かれたのと全く同じように書かれていますが、このメソッドはそのクラスの一部である必要があるのでクラスの中にメソッドをカプセル化する必要がある場合は役に立ちません場合。

@classmethod ファクトリメソッドを書きたいとき、そしてこのカスタム属性をクラスに結び付けることができるときは重要です。この属性は継承したクラスでオーバーライドできます。

これら2つの方法の比較は以下のようになります。

Table

33
SIslam

@ classmethod

@classmethod__init__と比較できます。別の__init__()と考えることができます。 pythonは、c ++でクラスコンストラクターのオーバーロードを実現する方法です。

class C:
    def __init__(self, parameters):
        ....

    @classmethod
    def construct_from_func(cls, parameters):
        ....

obj1 = C(parameters)
obj2 = C.construct_from_func(parameters)

__init__selfを使用しますが、construct_from_funcclsを使用しますが、どちらもdefinitioinの最初の引数としてクラスの参照を持っていることに注意してください。

@ staticmethod

@staticmethodobject methodと比較できます

class C:
    def __init__(self):
        ....

    @staticmethod
    def static_method(args):
        ....

    def normal_method(parameters):
        ....

result = C.static_method(parameters)
result = obj.normal_method(parameters)
1
Ke Zhang

つまり、@ classmehtodは通常のメソッドをファクトリメソッドに変換します。

例を使って探りましょう。

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'

@クラスメソッドがないと、インスタンスを1つずつ作成するのに手間がかかるはずです。

book1 = PythonBook('Learning Python', 'Mark Lutz')
In [20]: book1
Out[20]: Book: Learning Python, Author: Mark Lutz
book2 = PythonBook('Python Think', 'Allen B Dowey')
In [22]: book2
Out[22]: Book: Python Think, Author: Allen B Dowey

例えば@classmethodで

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'
    @classmethod
    def book1(cls):
        return cls('Learning Python', 'Mark Lutz')
    @classmethod
    def book2(cls):
        return cls('Python Think', 'Allen B Dowey')

試して:

In [31]: PythonBook.book1()
Out[31]: Book: Learning Python, Author: Mark Lutz
In [32]: PythonBook.book2()
Out[32]: Book: Python Think, Author: Allen B Dowey

見る?インスタンスはクラス定義内に正常に作成され、まとめて収集されます。

結論として、@ classmethodデコレータは従来のメソッドをファクトリメソッドに変換します。クラスメソッドを使用すると、必要なだけ代替コンストラクタを追加することができます。

1
JawSaw

私はこのサイトの初心者です、私は上のすべての答えを読みました、そして私が欲しい情報を得ました。しかし、私は投票する権利がありません。それで、私はそれを理解しているので答えと共にStackOverflowを始めようと思います。

  • @staticmethodは、メソッドの最初のパラメータとしてselfやclsを必要としません。
  • @staticmethodおよび@classmethodラップされた関数は、インスタンスまたはクラス変数によって呼び出される可能性があります
  • @staticmethod装飾関数は、サブクラスの継承が@staticmethodデコレータによってラップされている基本クラス関数を上書きできないというある種の「不変のプロパティ」に影響します。
  • @classmethodは、関数の最初のパラメータとしてcls(クラス名、必要ならば変数名を変更することができます)を必要とします
  • @classmethodは常にサブクラス方式で使用されます。サブクラスの継承は基本クラス関数の効果を変更する可能性があります。つまり、@classmethodラップされた基本クラス関数は異なるサブクラスによって上書きされる可能性があります。
1
Ayichen

誰かに役立つかもしれない少し異なる考え方...クラスメソッドは、スーパークラスで使用され、異なる子クラスから呼び出されたときのメソッドの動作を定義します。静的メソッドは、呼び出している子クラスに関係なく同じものを返したいときに使用されます。

0
boson