Wann sollte ich @classmethod verwenden und wann def method (self)?

87

Bei der Integration einer Django-App, die ich zuvor noch nicht verwendet habe, habe ich zwei verschiedene Möglichkeiten gefunden, um Funktionen in Klassen zu definieren. Der Autor scheint beide sehr absichtlich zu verwenden. Der erste ist einer, den ich selbst oft benutze:

class Dummy(object):

    def some_function(self,*args,**kwargs):
        do something here
        self is the class instance

Der andere ist einer, den ich nicht benutze, hauptsächlich, weil ich nicht verstehe, wann ich ihn verwenden soll und wofür:

class Dummy(object):

    @classmethod
    def some_function(cls,*args,**kwargs):
        do something here
        cls refers to what?

In den Python-Dokumenten wird der classmethodDekorateur mit folgendem Satz erklärt:

Eine Klassenmethode empfängt die Klasse als implizites erstes Argument, genau wie eine Instanzmethode die Instanz empfängt.

Ich clsbeziehe mich also auf sich Dummyselbst (die class, nicht die Instanz). Ich verstehe nicht genau, warum das so ist, weil ich das immer tun konnte:

type(self).do_something_with_the_class

Ist das nur der Klarheit halber oder habe ich den wichtigsten Teil verpasst: gruselige und faszinierende Dinge, die ohne sie nicht möglich wären?

marue
quelle

Antworten:

69

Ihre Vermutung ist richtig - Sie verstehen, wie es classmethod funktioniert.

Der Grund dafür ist, dass diese Methoden sowohl für eine Instanz als auch für die Klasse aufgerufen werden können (in beiden Fällen wird das Klassenobjekt als erstes Argument übergeben):

class Dummy(object):

    @classmethod
    def some_function(cls,*args,**kwargs):
        print cls

#both of these will have exactly the same effect
Dummy.some_function()
Dummy().some_function()

Zur Verwendung dieser Instanzen : Es gibt mindestens zwei Hauptverwendungen zum Aufrufen einer Klassenmethode für eine Instanz:

  1. self.some_function()ruft die Version von some_functionfür den tatsächlichen Typ von auf selfund nicht für die Klasse, in der dieser Aufruf gerade erscheint (und benötigt keine Aufmerksamkeit, wenn die Klasse umbenannt wird); und
  2. In Fällen, in denen some_functionein Protokoll implementiert werden muss, es jedoch nützlich ist, nur das Klassenobjekt aufzurufen.

Der Unterschied zustaticmethod : Es gibt eine andere Möglichkeit, Methoden zu definieren, die nicht auf Instanzdaten zugreifen staticmethod. Dadurch entsteht eine Methode, die überhaupt kein implizites erstes Argument erhält. Dementsprechend werden keine Informationen über die Instanz oder Klasse übergeben, für die es aufgerufen wurde.

In [6]: class Foo(object): some_static = staticmethod(lambda x: x+1)

In [7]: Foo.some_static(1)
Out[7]: 2

In [8]: Foo().some_static(1)
Out[8]: 2

In [9]: class Bar(Foo): some_static = staticmethod(lambda x: x*2)

In [10]: Bar.some_static(1)
Out[10]: 2

In [11]: Bar().some_static(1)
Out[11]: 2

Die Hauptanwendung, die ich dafür gefunden habe, besteht darin, eine vorhandene Funktion (die nicht erwartet, a zu erhalten self) als Methode für eine Klasse (oder ein Objekt) anzupassen .

Marcin
quelle
2
Schön: Ich mag es, wenn die Antwort in wie - warum aufgeteilt wird. Soweit ich es jetzt verstanden habe, ermöglicht @classmethod den Zugriff auf die Funktion, ohne dass eine Instanz erforderlich ist. Genau das habe ich gesucht, danke.
März
1
@marcin Echte Ententypisierung gibt ihm eine andere Dimension. Es ging mehr darum, Dinge nicht so zu schreiben, wie self.foo()es sein sollte Bar.foo().
Voo
5
@Voo Eigentlich self.foo()ist es vorzuziehen, da es sich selfmöglicherweise um eine Instanz einer Unterklasse handelt, die eine eigene implementiert foo.
Marcin
1
@Marcin Ich denke, das hängt davon ab, was Sie erreichen wollen. Aber ich verstehe deinen Standpunkt.
März
1
Sie sollten ein anderes Lambda für verwenden Bar, da 1+1 == 2 == 1*2es unmöglich ist, anhand der gezeigten Ergebnisse zu erkennen, dass Bar().static_methodtatsächlich aufgerufen wird.
George V. Reilly
-1

Grundsätzlich sollten Sie eine @classmethod verwenden, wenn Sie feststellen, dass die Definition der Methode nicht geändert oder überschrieben wird.

Ein weiteres Problem: Klassenmethoden sind theoretisch schneller als Objektmethoden, da sie nicht instanziiert werden müssen und weniger Speicher benötigen.

Joao Barbosa
quelle
-3

Wenn Sie decorator @classmethod hinzufügen, bedeutet dies, dass Sie diese Methode als statische Methode von Java oder C ++ erstellen. (statische Methode ist ein allgemeiner Begriff, denke ich ;) ) Python hat auch @staticmethod. Der Unterschied zwischen Klassenmethode und statischer Methode besteht darin, ob Sie mithilfe des Arguments oder des Klassennamens selbst auf eine Klasse oder eine statische Variable zugreifen können.

class TestMethod(object):
    cls_var = 1
    @classmethod
    def class_method(cls):
        cls.cls_var += 1
        print cls.cls_var

    @staticmethod
    def static_method():
        TestMethod.cls_var += 1
        print TestMethod.cls_var
#call each method from class itself.
TestMethod.class_method()
TestMethod.static_method()

#construct instances
testMethodInst1 = TestMethod()    
testMethodInst2 = TestMethod()   

#call each method from instances
testMethodInst1.class_method()
testMethodInst2.static_method()

Alle diese Klassen erhöhen cls.cls_var um 1 und drucken es aus.

Und jede Klasse, die denselben Namen in demselben Bereich oder denselben Instanzen verwendet, die mit dieser Klasse erstellt wurden, wird diese Methoden gemeinsam nutzen. Es gibt nur eine TestMethod.cls_var und auch nur eine TestMethod.class_method (), TestMethod.static_method ()

Und wichtige Frage. warum diese Methode benötigt würde.

Klassenmethode oder statische Methode ist nützlich, wenn Sie diese Klasse als Factory erstellen oder wenn Sie Ihre Klasse nur einmal initialisieren müssen. wie einmal Datei öffnen und Feed-Methode verwenden, um die Datei Zeile für Zeile zu lesen.

Ryan Kim
quelle
2
(a) Statische Methoden sind etwas anderes; (b) Klassenmethoden werden nicht von jeder Klasse geteilt.
Marcin
@Marcin danke, dass du mich über meine unklaren Definitionen und all das informiert hast.
Ryan Kim