Frage

Ich bringe mir selbst Python bei und meine letzte Lektion war das Python ist nicht Java, und so habe ich gerade eine Weile damit verbracht, alle meine Klassenmethoden in Funktionen umzuwandeln.

Mir ist jetzt klar, dass ich für das, was ich tun würde, keine Klassenmethoden verwenden muss static Methoden in Java, aber jetzt bin ich mir nicht sicher, wann ich sie verwenden würde.Alle Ratschläge, die ich zu Python-Klassenmethoden finden kann, gehen dahin, dass Neulinge wie ich die Finger davon lassen sollten, und die Standarddokumentation ist bei der Diskussion über sie am undurchsichtigsten.

Hat jemand ein gutes Beispiel für die Verwendung einer Klassenmethode in Python oder kann mir zumindest jemand sagen, wann Klassenmethoden sinnvoll eingesetzt werden können?

War es hilfreich?

Lösung

Klassenmethoden sind für den Fall gedacht, dass Sie Methoden benötigen, die nicht spezifisch für eine bestimmte Instanz sind, die Klasse aber dennoch in irgendeiner Weise einbeziehen.Das Interessanteste an ihnen ist, dass sie von Unterklassen überschrieben werden können, was in den statischen Methoden von Java oder den Funktionen auf Modulebene von Python einfach nicht möglich ist.

Wenn Sie eine Klasse haben MyClass, und eine Funktion auf Modulebene, die auf MyClass (Factory, Abhängigkeitsinjektions-Stub usw.) ausgeführt wird, machen es zu einem classmethod.Dann steht es Unterklassen zur Verfügung.

Andere Tipps

Factory-Methoden (alternative Konstruktoren) sind in der Tat ein klassisches Beispiel für Klassenmethoden.

Grundsätzlich eignen sich Klassenmethoden immer dann, wenn Sie eine Methode wünschen, die natürlich in den Namensraum der Klasse passt, aber keiner bestimmten Instanz der Klasse zugeordnet ist.

Als Beispiel im Exzellenten Unipath Modul:

Aktuelles Verzeichnis

  • Path.cwd()
    • Gibt das tatsächlich aktuelle Verzeichnis zurück;z.B., Path("/tmp/my_temp_dir").Dies ist eine Klassenmethode.
  • .chdir()
    • Machen Sie self zum aktuellen Verzeichnis.

Da das aktuelle Verzeichnis prozessweit ist, ist das cwd Die Methode hat keine bestimmte Instanz, mit der sie verknüpft werden sollte.Allerdings ändert sich die cwd in das Verzeichnis eines bestimmten Path Instanz sollte tatsächlich eine Instanzmethode sein.

Hmmm...als Path.cwd() gibt tatsächlich a zurück Path Ich denke, es könnte sich zum Beispiel um eine Factory-Methode handeln ...

Stellen Sie sich das so vor:Normale Methoden sind nützlich, um die Details des Versands auszublenden:du kannst Tippen myobj.foo() ohne sich Gedanken darüber zu machen, ob die foo() Die Methode wird von der implementiert myobj Die Klasse des Objekts oder eine seiner übergeordneten Klassen.Klassenmethoden sind genau analog dazu, jedoch mit dem Klassenobjekt:Sie lassen dich anrufen MyClass.foo() ohne sich Gedanken darüber machen zu müssen, ob foo() wird speziell von umgesetzt MyClass weil es eine eigene spezialisierte Version benötigte oder ob es den Aufruf seiner übergeordneten Klasse überließ.

Klassenmethoden sind für die Einrichtung oder Berechnung unerlässlich geht voraus die Erstellung einer tatsächlichen Instanz, denn solange die Instanz nicht existiert, können Sie die Instanz natürlich nicht als Versandpunkt für Ihre Methodenaufrufe verwenden.Ein gutes Beispiel finden Sie im SQLAlchemy-Quellcode.Schauen Sie sich das mal an dbapi() Klassenmethode unter folgendem Link:

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

Sie können sehen, dass die dbapi() Die Methode, die ein Datenbank-Backend verwendet, um die anbieterspezifische Datenbankbibliothek, die es bei Bedarf benötigt, zu importieren, ist eine Klassenmethode, da sie ausgeführt werden muss Vor Instanzen einer bestimmten Datenbankverbindung werden erstellt – es kann sich jedoch nicht um eine einfache Funktion oder statische Funktion handeln, da sie andere, unterstützende Methoden aufrufen soll, die möglicherweise ebenfalls spezifischer in Unterklassen als in ihren übergeordneten Klassen geschrieben werden müssen Klasse.Und wenn Sie an eine Funktion oder eine statische Klasse weiterleiten, „vergessen“ Sie und verlieren das Wissen darüber, welche Klasse die Initialisierung durchführt.

Ich wollte kürzlich eine sehr leichte Protokollierungsklasse, die abhängig von der Protokollierungsstufe, die programmgesteuert festgelegt werden kann, unterschiedliche Ausgabemengen ausgibt.Aber ich wollte die Klasse nicht jedes Mal instanziieren, wenn ich eine Debugging-Meldung, einen Fehler oder eine Warnung ausgeben wollte.Aber ich wollte auch die Funktionsweise dieser Protokollierungsfunktion kapseln und sie ohne die Deklaration von Globals wiederverwendbar machen.

Also habe ich Klassenvariablen und das verwendet @classmethod Dekorateur, um dies zu erreichen.

Mit meiner einfachen Logging-Klasse könnte ich Folgendes tun:

Logger._level = Logger.DEBUG

Wenn ich dann in meinem Code eine Menge Debugging-Informationen ausspucken wollte, musste ich einfach codieren

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

Fehler könnten mit gelöscht werden

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

In der „Produktions“-Umgebung kann ich angeben

Logger._level = Logger.ERROR

und jetzt wird nur noch die Fehlermeldung ausgegeben.Die Debug-Meldung wird nicht gedruckt.

Hier ist meine Klasse:

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

Und ein Code, der es ein wenig testet:

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()

Alternative Konstruktoren sind das klassische Beispiel.

Ich denke, die klarste Antwort ist AmanKows eins.Es kommt darauf an, wie Sie Ihren Code organisieren möchten.Sie können alles als Funktionen auf Modulebene schreiben, die in den Namensraum des Moduls eingeschlossen sind, d. h

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()

Der obige Verfahrenscode ist nicht gut organisiert, wie Sie sehen können, wird es nach nur 3 Modulen verwirrend. Was machen die einzelnen Methoden?Sie können lange beschreibende Namen für Funktionen verwenden (wie in Java), aber Ihr Code wird trotzdem sehr schnell unüberschaubar.

Die objektorientierte Methode besteht darin, Ihren Code in verwaltbare Blöcke zu zerlegen, d. h. Klassen, Objekte und Funktionen können Objektinstanzen oder Klassen zugeordnet werden.

Mit Klassenfunktionen erhalten Sie im Vergleich zu Funktionen auf Modulebene eine weitere Unterteilungsebene in Ihrem Code.So können Sie verwandte Funktionen innerhalb einer Klasse gruppieren, um sie spezifischer für eine Aufgabe zu machen, die Sie dieser Klasse zugewiesen haben.Sie können beispielsweise eine Dateidienstprogrammklasse erstellen:

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")

Auf diese Weise ist es flexibler und wartbarer, Sie gruppieren Funktionen und es ist deutlicher erkennbar, was die einzelnen Funktionen tun.Außerdem vermeiden Sie Namenskonflikte, wenn die Funktionskopie beispielsweise in einem anderen importierten Modul (z. B. einer Netzwerkkopie) vorhanden sein könnte, das Sie in Ihrem Code verwenden. Wenn Sie also den vollständigen Namen FileUtil.copy() verwenden, beseitigen Sie das Problem und beide Kopierfunktionen können nebeneinander verwendet werden.

Wenn sich ein Benutzer auf meiner Website anmeldet, wird aus dem Benutzernamen und dem Passwort ein User()-Objekt instanziiert.

Wenn ich ein Benutzerobjekt benötige, ohne dass der Benutzer dort ist, um sich anzumelden (z. B.Ein Admin-Benutzer möchte möglicherweise das Konto eines anderen Benutzers löschen, daher muss ich diesen Benutzer instanziieren und seine Löschmethode aufrufen):

Ich habe Klassenmethoden, um das Benutzerobjekt abzurufen.

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()

Ehrlich?Ich habe nie eine Verwendung für staticmethod oder classmethod gefunden.Ich habe noch keinen Vorgang gesehen, der nicht mit einer globalen Funktion oder einer Instanzmethode ausgeführt werden kann.

Es wäre anders, wenn Python eher wie Java private und geschützte Mitglieder verwenden würde.In Java benötige ich eine statische Methode, um auf die privaten Mitglieder einer Instanz zugreifen und Aufgaben erledigen zu können.In Python ist das selten notwendig.

Normalerweise sehe ich Leute, die statische Methoden und Klassenmethoden verwenden, obwohl sie eigentlich nur die Namespaces auf Modulebene von Python besser nutzen müssen.

Sie können damit generische Klassenmethoden schreiben, die Sie mit jeder kompatiblen Klasse verwenden können.

Zum Beispiel:

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

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

Wenn Sie es nicht verwenden @classmethod Sie können es mit dem Schlüsselwort self tun, es benötigt jedoch eine Instanz von 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

Ich habe früher mit PHP gearbeitet und habe mich kürzlich gefragt, was mit dieser Klassenmethode los ist.Das Python-Handbuch ist sehr technisch und sehr kurz formuliert, sodass es nicht zum Verständnis dieser Funktion beiträgt.Ich habe gegoogelt und gegoogelt und eine Antwort gefunden -> http://code.anjanesh.net/2007/12/python-classmethods.html.

Wenn Sie faul sind, darauf zu klicken.Meine Erklärung ist kürzer und weiter unten.:) :)

In PHP (vielleicht kennen nicht alle von Ihnen PHP, aber diese Sprache ist so einfach, dass jeder verstehen sollte, wovon ich spreche) haben wir statische Variablen wie diese:


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();

Die Ausgabe beträgt in beiden Fällen 20.

In Python können wir jedoch den @classmethod-Dekorator hinzufügen und somit ist es möglich, die Ausgabe 10 bzw. 20 zu erhalten.Beispiel:


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()

Schlau, nicht wahr?

Klassenmethoden bieten einen „semantischen Zucker“ (ich weiß nicht, ob dieser Begriff weit verbreitet ist) – oder „semantische Bequemlichkeit“.

Beispiel:Sie haben eine Reihe von Klassen, die Objekte darstellen.Möglicherweise möchten Sie die Klassenmethode haben all() oder find() schreiben User.all() oder User.find(firstname='Guido').Das könnte natürlich mit Funktionen auf Modulebene erfolgen ...

Was mir bei Ruby gerade aufgefallen ist, ist, dass es sich um einen sogenannten handelt Klasse Methode und eine sog Beispiel Methode ist nur eine Funktion mit semantischer Bedeutung, die auf ihren ersten Parameter angewendet wird und stillschweigend übergeben wird, wenn die Funktion wird als Methode von an aufgerufen Objekt (d. h. obj.meth()).

Normalerweise das Objekt muss eine Instanz sein, aber die @classmethod Methodendekorateur Ändert die Regeln, um eine Klasse zu bestehen.Sie können eine Klassenmethode für eine Instanz aufrufen (es ist nur eine Funktion) – das erste Argument ist ihre Klasse.

Denn es ist nur ein Funktion, es kann nur einmal in einem bestimmten Bereich deklariert werden (d. h. class Definition).Daraus folgt, als Überraschung für einen Rubyisten, das Sie können keine Klassenmethode und keine Instanzmethode mit demselben Namen haben.

Bedenken Sie:

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

Du kannst anrufen foo auf einer Instanz

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

Aber nicht in einer Klasse:

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)

Jetzt hinzufügen @classmethod:

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

Beim Aufruf einer Instanz wird nun deren Klasse übergeben:

Foo().foo()
__main__.Foo

ebenso wie der Aufruf einer Klasse:

Foo.foo()
__main__.Foo

Es sind nur Konventionen, die uns vorschreiben, was wir verwenden self für das erste Argument einer Instanzmethode und cls auf einer Klassenmethode.Ich habe hier weder das eine noch das andere verwendet, um zu veranschaulichen, dass es sich nur um ein Argument handelt.In Ruby, self ist ein Stichwort.

Im Gegensatz zu 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>

Die Python-Klassenmethode ist nur eine dekorierte Funktion und Sie können die gleichen Techniken verwenden, um Erstellen Sie Ihre eigenen Dekorateure.Eine dekorierte Methode umschließt die echte Methode (im Fall von @classmethod es übergibt das zusätzliche Klassenargument).Die zugrunde liegende Methode ist immer noch verborgen aber immer noch zugänglich.


Fußnote:Ich habe dies geschrieben, nachdem ein Namenskonflikt zwischen einer Klasse und einer Instanzmethode meine Neugier geweckt hatte.Ich bin alles andere als ein Python-Experte und würde mich über Kommentare freuen, wenn irgendetwas davon falsch ist.

Das ist ein interessantes Thema.Meiner Meinung nach funktioniert die Python-Klassenmethode wie ein Singleton und nicht wie eine Fabrik (die eine erzeugte Instanz einer Klasse zurückgibt).Der Grund dafür, dass es sich um einen Singleton handelt, liegt darin, dass ein gemeinsames Objekt (das Wörterbuch) erstellt wird, jedoch nur einmal für die Klasse, aber von allen Instanzen gemeinsam genutzt wird.

Um dies zu veranschaulichen, hier ein Beispiel.Beachten Sie, dass alle Instanzen einen Verweis auf das einzelne Wörterbuch haben.Dies ist kein Factory-Muster, wie ich es verstehe.Dies ist wahrscheinlich sehr einzigartig für 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

Ich habe mir einige Male die gleiche Frage gestellt.Und obwohl die Leute hier sich Mühe gegeben haben, es zu erklären, ist meiner Meinung nach die beste (und einfachste) Antwort, die ich gefunden habe Beschreibung der Class-Methode in der Python-Dokumentation.

Es gibt auch einen Verweis auf die statische Methode.Und falls jemand bereits Instanzmethoden kennt (was ich annehme), könnte diese Antwort der letzte Teil sein, der alles zusammenfügt ...

Weitere und tiefergehende Erläuterungen zu diesem Thema finden Sie auch in der Dokumentation:Die Standardtyphierarchie (nach unten scrollen zu Instanzmethoden Abschnitt)

Eine Klasse definiert natürlich eine Reihe von Instanzen.Und die Methoden einer Klasse wirken auf die einzelnen Instanzen.Die Klassenmethoden (und Variablen) bieten einen Platz zum Aufhängen anderer Informationen, die sich auf die Gesamtheit der Instanzen beziehen.

Wenn Ihre Klasse beispielsweise eine Gruppe von Schülern definiert, benötigen Sie möglicherweise Klassenvariablen oder -methoden, die Dinge wie die Klassengruppe definieren, zu der die Schüler gehören können.

Sie können Klassenmethoden auch verwenden, um Werkzeuge für die Arbeit am gesamten Satz zu definieren.Beispielsweise könnte Student.all_of_em() alle bekannten Studenten zurückgeben.Wenn Ihre Instanzenmenge mehr Struktur als nur eine Menge hat, können Sie natürlich Klassenmethoden bereitstellen, um diese Struktur zu kennen.Students.all_of_em(grade='juniors')

Techniken wie diese führen tendenziell dazu, Mitglieder der Instanzenmenge in Datenstrukturen zu speichern, die in Klassenvariablen verwurzelt sind.Sie müssen dann darauf achten, dass die Speicherbereinigung nicht zunichte gemacht wird.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top