クラスメソッドの目的は何ですか?


250

私は自分にPythonを教えていますが、最近のレッスンでは、PythonはJavaはないということでした。そのため、すべてのClassメソッドを関数に変換するのにしばらく時間がかかりました。

staticJava でメソッドを使用する場合にClassメソッドを使用する必要がないことに気付きましたが、今はいつ使用するのかわかりません。Pythonクラスのメソッドについて私が見つけることができるすべてのアドバイスは、私がそれらを避けるべきであるような初心者のラインに沿っており、標準のドキュメントはそれらを議論するときに最も不透明です。

誰かがPythonでClassメソッドを使用する良い例を持っていますか、少なくとも誰かがClassメソッドが賢明に使用できるときを教えてもらえますか?

回答:


178

クラスメソッドは、特定のインスタンスに固有ではないが、何らかの方法でクラスに関与するメソッドが必要な場合に使用します。それらの中で最も興味深いのは、サブクラスによってオーバーライドできることです。これは、Javaの静的メソッドやPythonのモジュールレベルの関数では不可能です。

クラスMyClassと、MyClassを操作するモジュールレベルの関数(ファクトリー、依存関係注入スタブなど)がある場合は、それをにしclassmethodます。その後、サブクラスで使用できるようになります。


そうですか。しかし、MyClassを必要とせず、他のMyClassにグループ化した場合でも、何らかの意味でメソッドを分類したい場合はどうでしょうか。私はそれをヘルパーモジュールに移動することを考えることができます..
swdev

元の質問に関連した質問があります。おそらく、同時に答えてもらえますか?オブジェクトのクラスメソッドを呼び出す方法は、「より良い」または「より慣用的」のobj.cls_mthd(...)どちらtype(obj).cls_mthd(...)ですか。
アレクセイ2018

63

ファクトリーメソッド(代替コンストラクター)は、確かにクラスメソッドの典型的な例です。

基本的に、クラスメソッドは、クラスの名前空間に自然に適合するが、クラスの特定のインスタンスに関連付けられていないメソッドが必要な場合はいつでも適しています。

例として、優れたunipathモジュールでは:

カレントディレクトリ

  • Path.cwd()
    • 実際の現在のディレクトリを返します。例えば、Path("/tmp/my_temp_dir")。これはクラスメソッドです。
  • .chdir()
    • 自分自身を現在のディレクトリにします。

現在のディレクトリはプロセス全体にわたるため、cwdメソッドには、関連付けられる特定のインスタンスはありません。ただし、cwd特定のPathインスタンスのディレクトリに変更することは、実際にはインスタンスメソッドである必要があります。

うーん... Path.cwd()実際にPathインスタンスを返すように、私はそれがファクトリーメソッドであると考えることができると思います...


1
うん...ファクトリーメソッドが最初に思い浮かんだ。また、これらの行に沿って、次のようなシングルトンパターンを実装するものがあります。getAndOptionallyCreateSomeSingleInstanceOfSomeResource()
Jemenake

3
Pythonでは、これらは静的メソッドであり、クラスメソッドではありません。
Jaykul 2014

46

このように考えてください。通常のメソッドはディスパッチの詳細を隠すのに役立ちます。メソッドがオブジェクトのクラスによって実装されているのか、その親クラスの1つによって実装されているのmyobj.foo()かを気にすることなく入力できます。クラスメソッドはこれとまったく同じですが、代わりにクラスオブジェクトを使用します。独自の特別なバージョンが必要だったために特別に実装されているかどうか、または親クラスに呼び出しを処理させているかどうかを気にすることなく呼び出すことができます。foo()myobjMyClass.foo()foo()MyClass

実際にインスタンスが作成される前にセットアップまたは計算を行う場合、クラスメソッドは不可欠です。インスタンスが存在するまで、そのインスタンスをメソッド呼び出しのディスパッチポイントとして使用することはできないためです。良い例はSQLAlchemyソースコードで見ることができます。見とるdbapi()次のリンクでクラスメソッドを:

https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47

dbapi()データベースバックエンドがオンデマンドで必要なベンダー固有のデータベースライブラリをインポートするために使用するメソッドは、特定のデータベース接続のインスタンスが作成される前に実行する必要があるため、クラスメソッドであることがわかります。単純な関数または静的関数である必要があります。同様に、親クラスではなくサブクラスでより具体的に記述する必要がある他のサポートメソッドを呼び出すことができるようにするためです。そして、関数または静的クラスにディスパッチすると、「忘れて」、どのクラスが初期化を行っているかについての知識が失われます。


リンク切れ、調整してください
piertoni 2015

28

私は最近、プログラムで設定できるロギングレベルに応じてさまざまな量の出力を出力する、非常に軽量なロギングクラスが必要でした。しかし、デバッグメッセージ、エラー、または警告を出力するたびにクラスをインスタンス化したくありませんでした。しかし、このロギング機能の機能をカプセル化し、グローバルを宣言せずに再利用できるようにしたいと思っていました。

そこで、クラス変数と@classmethodデコレーターを使用してこれを実現しました。

私の単純なLoggingクラスを使用すると、次のことができます。

Logger._level = Logger.DEBUG

次に、私のコードで、大量のデバッグ情報を吐き出したい場合は、

Logger.debug( "this is some annoying message I only want to see while debugging" )

エラーは出力される可能性があります

Logger.error( "Wow, something really awful happened." )

「本番」環境では、指定できます

Logger._level = Logger.ERROR

そして今、エラーメッセージのみが出力されます。デバッグメッセージは出力されません。

これが私のクラスです:

class Logger :
    ''' Handles logging of debugging and error messages. '''

    DEBUG = 5
    INFO  = 4
    WARN  = 3
    ERROR = 2
    FATAL = 1
    _level = DEBUG

    def __init__( self ) :
        Logger._level = Logger.DEBUG

    @classmethod
    def isLevel( cls, level ) :
        return cls._level >= level

    @classmethod
    def debug( cls, message ) :
        if cls.isLevel( Logger.DEBUG ) :
            print "DEBUG:  " + message

    @classmethod
    def info( cls, message ) :
        if cls.isLevel( Logger.INFO ) :
            print "INFO :  " + message

    @classmethod
    def warn( cls, message ) :
        if cls.isLevel( Logger.WARN ) :
            print "WARN :  " + message

    @classmethod
    def error( cls, message ) :
        if cls.isLevel( Logger.ERROR ) :
            print "ERROR:  " + message

    @classmethod
    def fatal( cls, message ) :
        if cls.isLevel( Logger.FATAL ) :
            print "FATAL:  " + message

そして、それを少しだけテストするいくつかのコード:

def logAll() :
    Logger.debug( "This is a Debug message." )
    Logger.info ( "This is a Info  message." )
    Logger.warn ( "This is a Warn  message." )
    Logger.error( "This is a Error message." )
    Logger.fatal( "This is a Fatal message." )

if __name__ == '__main__' :

    print "Should see all DEBUG and higher"
    Logger._level = Logger.DEBUG
    logAll()

    print "Should see all ERROR and higher"
    Logger._level = Logger.ERROR
    logAll()

10
これは、すべてのクラスメソッドをモジュールレベルの関数にして、クラスを完全に削除するだけで、それほど複雑ではない方法で実行できるので、優れたサンプルクラスメソッドが適していると思えませ
martineau

10
さらに重要なことに、Pythonは非常に使いやすいロギングクラスを提供します。最適ではないソリューションを作成するよりも悪いため、私はホイールを再発明しました。ダブルハンドスラップ。しかし、それは少なくとも実用的な例を提供します。ただし、概念的なレベルでクラスを取り除くことに同意するかどうかはわかりません。簡単に再利用できるようにコードをカプセル化したい場合もあり、ロギングメソッドは再利用の優れたターゲットです。
Marvo、2011年

3
静的メソッドを使用しないのはなぜですか?
bdforbes 2011年


11

ユーザーが私のWebサイトにログインすると、ユーザー名とパスワードからUser()オブジェクトがインスタンス化されます。

ユーザーがログインしていない状態でユーザーオブジェクトが必要な場合(たとえば、管理ユーザーが別のユーザーアカウントを削除したい場合は、そのユーザーをインスタンス化してその削除メソッドを呼び出す必要があります):

ユーザーオブジェクトを取得するクラスメソッドがあります。

class User():
    #lots of code
    #...
    # more code

    @classmethod
    def get_by_username(cls, username):
        return cls.query(cls.username == username).get()

    @classmethod
    def get_by_auth_id(cls, auth_id):
        return cls.query(cls.auth_id == auth_id).get()

9

最も明確な答えはアマンコウの答えだと思います。つまり、コードをどのように整理したいかということです。すべてを、モジュールの名前空間にラップされたモジュールレベルの関数として記述できます。

module.py (file 1)
---------
def f1() : pass
def f2() : pass
def f3() : pass


usage.py (file 2)
--------
from module import *
f1()
f2()
f3()
def f4():pass 
def f5():pass

usage1.py (file 3)
-------------------
from usage import f4,f5
f4()
f5()

上記の手続き型コードは、3つのモジュールだけで混乱するようになっているので、うまく整理されていません。各メソッドは何をしているのですか?(javaのように)関数に長い説明名を使用できますが、コードは非常に迅速に管理できなくなります。

オブジェクト指向の方法は、コードを管理可能なブロックに分解することです。つまり、クラスとオブジェクトおよび関数をオブジェクトインスタンスまたはクラスに関連付けることができます。

クラス関数を使用すると、モジュールレベルの関数と比較して、コードで別のレベルの分割が得られます。そのため、クラス内の関連する関数をグループ化して、そのクラスに割り当てたタスクに固有のものにすることができます。たとえば、ファイルユーティリティクラスを作成できます。

class FileUtil ():
  def copy(source,dest):pass
  def move(source,dest):pass
  def copyDir(source,dest):pass
  def moveDir(source,dest):pass

//usage
FileUtil.copy("1.txt","2.txt")
FileUtil.moveDir("dir1","dir2")

この方法はより柔軟で保守しやすく、関数をグループ化し、各関数が何をするのかを明確にします。また、名前の競合を防ぐことができます。たとえば、コードで使用するインポートされた別のモジュール(ネットワークコピーなど)に関数のコピーが存在する可能性があるため、フルネームFileUtil.copy()を使用すると、問題と両方のコピー関数が削除されます。並べて使用できます。


1
f1()、f2()などを使用するために、あなたがしたように、from module import *およびfrom usage import *を使用すべきではありませんか?
Jblasco、2015年

@Jblascoインポート文を修正して混乱を取り除きました。モジュールをインポートする場合は、関数の前にモジュール名を付ける必要があります。つまり、インポートモジュール-> module.f1()など
firephil

私はこの答えに同意しません。PythonはJavaではありません。管理不能なコードの問題は、例で意図的に不適切な名前を選択したために発生しているようです。代わりに、FileUtilクラスメソッドをfile_utilモジュールの関数としてレプリケートした場合、オブジェクトは実際に存在しないときに比較可能であり、OOPを乱用しません(実際には、from file_util import FileUtilまたは他の冗長性に終わらないため、それが望ましいと主張できます)。名前の競合は、のimport file_util代わりにを実行することにより、手続き型コードでも同様に回避できfrom file_util import ...ます。
ForgottenUmbrella

7

正直なところ?staticmethodまたはclassmethodの使用法を見つけたことはありません。グローバル関数やインスタンスメソッドを使用して実行できない操作はまだ見ていません。

PythonがJavaのようにプライベートおよび保護されたメンバーを使用する場合は異なります。Javaでは、インスタンスのプライベートメンバーにアクセスして処理を行うための静的メソッドが必要です。Pythonでは、それが必要になることはめったにありません。

通常、静的メソッドとクラスメソッドを実際に使用する必要があるのは、Pythonのモジュールレベルの名前空間を使用することだけです。


1
Private:_variable_name and Protected:__variable_name
Bradley

1
unittest setUpClassとtearDownClass は必須の用途の1つです。ユニットテストを使用していますよね?:)
dbn '13

7

これにより、互換性のあるクラスで使用できるジェネリッククラスメソッドを記述できます。

例えば:

@classmethod
def get_name(cls):
    print cls.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

使用しない場合は@classmethod、selfキーワードで実行できますが、Classのインスタンスが必要です。

def get_name(self):
    print self.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C().get_name() #<-note the its an instance of class C

5

私は以前PHPを使用していましたが、最近、このクラスメソッドで何が起こっているのかと自問していました。Pythonマニュアルは非常に技術的で、言葉が非常に短いため、その機能を理解するのに役立ちません。私はグーグルでググって、答えを見つけました-> http://code.anjanesh.net/2007/12/python-classmethods.html

あなたがそれをクリックするのが面倒なら。私の説明は短く、以下です。:)

PHPでは(皆さんがPHPを知っているとは限りませんが、この言語は非常に単純なので、誰もが私が話していることを理解する必要があります)次のような静的変数があります。


class A
{

    static protected $inner_var = null;

    static public function echoInnerVar()
    {
        echo self::$inner_var."\n";
    }

    static public function setInnerVar($v)
    {
        self::$inner_var = $v;
    }

}

class B extends A
{
}

A::setInnerVar(10);
B::setInnerVar(20);

A::echoInnerVar();
B::echoInnerVar();

出力はどちらの場合も20になります。

ただし、Pythonでは@classmethodデコレーターを追加できるため、それぞれ出力10と20を持つことができます。例:


class A(object):
    inner_var = 0

    @classmethod
    def setInnerVar(cls, value):
        cls.inner_var = value

    @classmethod
    def echoInnerVar(cls):
        print cls.inner_var


class B(A):
    pass


A.setInnerVar(10)
B.setInnerVar(20)

A.echoInnerVar()
B.echoInnerVar()

スマートではないですか?


あなたのPythonの例の一つの潜在的な問題があればあるB.setInnerVar(20)取り残された、それは印刷し10二回(というに過ぎないがことを第二echoInnerBar()の呼び出しでエラーを与えてinner_var定義されました。
マーティ

5

クラスメソッドは、「セマンティックシュガー」(この用語が広く使用されているかどうかはわかりません)または「セマンティック利便性」を提供します。

例:オブジェクトを表す一連のクラスを取得しました。クラスメソッドを使用するall()find()、またはを記述したいUser.all()場合がありUser.find(firstname='Guido')ます。もちろん、モジュールレベルの関数を使用して行うことができます...


もちろん、この例では、クラスがすべてのインスタンスを追跡し、インスタンスが作成された後にそれらにアクセスできると想定しています。これは自動的には行われません。
martineau

1
「セマンティックシュガー」は、「構文上のシュガー」とよく似ています。
XTL

3

Rubyから来た私を襲ったのは、いわゆるクラスメソッドといわゆるインスタンスメソッドが、最初のパラメーターにセマンティックな意味が適用された単なる関数であり、関数オブジェクト(すなわちobj.meth())。

通常、そのオブジェクトはインスタンスでなければなりませんが、@classmethod メソッドデコレータはルールを変更してクラスを渡します。インスタンスでクラスメソッドを呼び出すことができます(これは単なる関数です)。最初の引数はそのクラスになります。

これは単なる関数であるため、特定のスコープ(つまり、class定義)で1回だけ宣言できます。したがって、Rubyistへの驚きとして、以下の場合、同じ名前のクラスメソッドとインスタンスメソッドを持つことはできません

このことを考慮:

class Foo():
  def foo(x):
    print(x)

fooインスタンスを呼び出すことができます

Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>

しかしクラスではありません:

Foo.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)

追加し@classmethodます:

class Foo():
  @classmethod
  def foo(x):
    print(x)

インスタンスを呼び出すと、そのクラスが渡されます。

Foo().foo()
__main__.Foo

クラスの呼び出しと同様に:

Foo.foo()
__main__.Foo

selfインスタンスメソッドとclsクラスメソッドの最初の引数に使用することを指示するのは、唯一の規則です。ここではどちらも、それが単なる議論であることを示すために使用していません。Rubyではselfキーワードです。

Rubyと対比:

class Foo
  def foo()
    puts "instance method #{self}"
  end
  def self.foo()
    puts "class method #{self}"
  end
end

Foo.foo()
class method Foo

Foo.new.foo()
instance method #<Foo:0x000000020fe018>

Pythonクラスメソッドは装飾された関数であり、同じ手法を使用して独自のデコレータ作成できます。デコレートされたメソッドは、実際のメソッドをラップします(@classmethod追加のクラス引数を渡す場合)。基になるメソッドはまだ存在し、非表示ですがアクセス可能です。


脚注:クラスとインスタンスメソッドの名前の衝突が私の好奇心を刺激した後で、私はこれを書きました。私はPythonのエキスパートにはほど遠いので、これが間違っている場合はコメントをお願いします。


「関数がオブジェクトのメソッドとして呼び出されたときに暗黙のうちに渡される」-これは正確ではないと私は信じています。AFICT、Pythonでは何も「黙って渡され」ません。IMO、Pythonはこの意味でRubyよりはるかに賢明です(を除くsuper())。AFICT、「マジック」は、属性が設定または読み取られるときに発生します。obj.meth(arg)Pythonでの呼び出し(Rubyとは異なります)は単にを意味します(obj.meth)(arg)。何も通知されずにどこにも渡されobj.methず、作成元の関数よりも少ない引数を1つ受け入れる呼び出し可能なオブジェクトです。
Alexey

obj.methは、順番に、単にを意味しますgetattr(obj, "meth")
Alexey

他の一般的な言語から来る人々に合わせた答えを持っていることは理にかなっています。ただし、この会話から欠落している1つは、それを結び付けるものであり、Python記述子の概念です。関数は記述子であり、関数がクラスの属性である場合、最初の引数が「自動的に」インスタンスに渡されます。これはクラスメソッドの実装方法でもあり、純粋なpython実装は私がリンクしたHOWTOで提供されています。それはまた、デコレータであるという事実は、一種の補助的なのである
juanpa.arrivillaga

2

これは興味深いトピックです。私の見解では、Pythonのclassmethodは、ファクトリ(生成されたクラスのインスタンスを返す)ではなく、シングルトンのように動作します。シングルトンである理由は、生成される共通のオブジェクト(ディクショナリ)はあるが、クラスに対して1回だけで、すべてのインスタンスによって共有されるためです。

ここでこれを説明するのが例です。すべてのインスタンスが単一の辞書への参照を持っていることに注意してください。私が理解しているように、これはFactoryパターンではありません。これはおそらくpythonに非常に固有のものです。

class M():
 @classmethod
 def m(cls, arg):
     print "arg was",  getattr(cls, "arg" , None),
     cls.arg = arg
     print "arg is" , cls.arg

 M.m(1)   # prints arg was None arg is 1
 M.m(2)   # prints arg was 1 arg is 2
 m1 = M()
 m2 = M() 
 m1.m(3)  # prints arg was 2 arg is 3  
 m2.m(4)  # prints arg was 3 arg is 4 << this breaks the factory pattern theory.
 M.m(5)   # prints arg was 4 arg is 5

2

私は何度も同じ質問をしていました。そして、ここの人たちはそれを説明しようと一生懸命努力しましたが、私が見つけた最良の(そして最も簡単な)答えは、PythonドキュメントのClassメソッドの説明です。

Staticメソッドへの参照もあります。そして、誰かがインスタンスメソッド(私が仮定している)をすでに知っている場合、この答えはそれをすべてまとめる最後の部分になるかもしれません...

このトピックに関するさらに詳しい説明は、次のドキュメントにも記載されています。 標準タイプの階層インスタンスメソッドセクションまでスクロールしてください)


1

@classmethod外部リソースからそのクラスのオブジェクトを簡単にインスタンス化するのに役立ちます。以下を検討してください。

import settings

class SomeClass:
    @classmethod
    def from_settings(cls):
        return cls(settings=settings)

    def __init__(self, settings=None):
        if settings is not None:
            self.x = settings['x']
            self.y = settings['y']

次に別のファイルで:

from some_package import SomeClass

inst = SomeClass.from_settings()

inst.xにアクセスすると、settings ['x']と同じ値が得られます。


0

もちろん、クラスはインスタンスのセットを定義します。また、クラスのメソッドは個々のインスタンスで機能します。クラスメソッド(および変数)は、インスタンスのセットに関連する他の情報をすべてぶら下げる場所です。

たとえば、クラスが生徒のセットを定義している場合、生徒が所属できる成績のセットなどを定義するクラス変数またはメソッドが必要になる場合があります。

クラスメソッドを使用して、セット全体で作業するためのツールを定義することもできます。たとえば、Student.all_of_em()はすべての既知の学生を返す可能性があります。明らかに、インスタンスのセットが単なるセットよりも多くの構造を持っている場合、その構造について知るためのクラスメソッドを提供できます。Student.all_of_em(grade = 'juniors')

このような手法は、インスタンスのセットのメンバーを、クラス変数をルートとするデータ構造に格納する傾向があります。そのときは、ガベージコレクションを苛立たせないように注意する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.