Modulfunktion vs statische Methode vs Klassenmethode vs keine Dekorateure: Welche Redewendung ist pythonischer?

71

Ich bin ein Java-Entwickler, der mit Python ein- und ausgeschaltet hat. Ich bin kürzlich auf diesen Artikel gestoßen, in dem häufige Fehler erwähnt werden, die Java-Programmierer machen, wenn sie Python lernen. Der erste fiel mir auf:

Eine statische Methode in Java wird nicht in eine Python-Klassenmethode übersetzt. Natürlich führt dies zu mehr oder weniger demselben Effekt, aber das Ziel einer Klassenmethode besteht darin, etwas zu tun, das in Java normalerweise nicht einmal möglich ist (wie das Erben eines nicht standardmäßigen Konstruktors). Die idiomatische Übersetzung einer statischen Java-Methode ist normalerweise eine Funktion auf Modulebene, keine Klassenmethode oder statische Methode. (Und statische Endfelder sollten in Konstanten auf Modulebene übersetzt werden.)

Dies ist kein großes Leistungsproblem, aber ein Python-Programmierer, der mit Java-Idiom-Code wie diesem arbeiten muss, wird durch die Eingabe von Foo.Foo.someMethod ziemlich irritiert, wenn es nur Foo.someFunction sein sollte. Beachten Sie jedoch, dass das Aufrufen einer Klassenmethode eine zusätzliche Speicherzuweisung beinhaltet, die beim Aufrufen einer statischen Methode oder Funktion nicht erfolgt.

Oh, und all diese Foo.Bar.Baz-Attributketten sind auch nicht kostenlos. In Java werden diese gepunkteten Namen vom Compiler nachgeschlagen, sodass es zur Laufzeit wirklich egal ist, wie viele davon Sie haben. In Python werden die Suchvorgänge zur Laufzeit ausgeführt, sodass jeder Punkt zählt. (Denken Sie daran, dass in Python "Flat ist besser als verschachtelt", obwohl es eher mit "Lesbarkeitszählungen" und "Einfach ist besser als komplex" zu tun hat, als mit Leistung.)

Ich fand das etwas seltsam, weil die Dokumentation für staticmethod lautet:

Statische Methoden in Python ähneln denen in Java oder C ++. Siehe auch classmethod () für eine Variante, die zum Erstellen alternativer Klassenkonstruktoren nützlich ist.

Noch rätselhafter ist, dass dieser Code:

class A:
    def foo(x):
        print(x)
A.foo(5)

Schlägt in Python 2.7.3 wie erwartet fehl, funktioniert aber in 3.2.3 einwandfrei (obwohl Sie die Methode nicht für eine Instanz von A aufrufen können, sondern nur für die Klasse.)

Es gibt also drei Möglichkeiten, statische Methoden zu implementieren (vier, wenn Sie mit der Klassenmethode zählen), jede mit subtilen Unterschieden, von denen eine scheinbar nicht dokumentiert ist. Dies scheint im Widerspruch zu Pythons Mantra zu stehen. Es sollte einen - und vorzugsweise nur einen - offensichtlichen Weg geben, dies zu tun. Welche Redewendung ist die pythonischste? Was sind die Vor- und Nachteile von jedem?

Folgendes verstehe ich bisher:

Modulfunktion:

  • Vermeidet das Problem Foo.Foo.f ()
  • Verschmutzt den Namespace des Moduls mehr als die Alternativen
  • Keine Vererbung

statische Methode:

  • Hält Funktionen, die sich auf die Klasse beziehen, innerhalb der Klasse und außerhalb des Modul-Namespace.
  • Ermöglicht das Aufrufen der Funktion für Instanzen der Klasse.
  • Unterklassen können die Methode überschreiben.

Klassenmethode:

  • Identisch mit staticmethod, übergibt aber auch die Klasse als erstes Argument.

Normale Methode (nur Python 3) :

  • Identisch mit staticmethod, kann die Methode jedoch nicht für Instanzen der Klasse aufrufen.

Überdenke ich das? Ist das kein Problem? Bitte helfen Sie!

Doval
quelle

Antworten:

90

Der einfachste Weg, darüber nachzudenken, besteht darin, darüber nachzudenken, welche Art von Objekt die Methode benötigt, um ihre Arbeit zu erledigen. Wenn Ihre Methode Zugriff auf eine Instanz benötigt, machen Sie sie zu einer regulären Methode. Wenn es Zugriff auf die Klasse benötigt, machen Sie es zu einer Klassenmethode. Wenn es keinen Zugriff auf die Klasse oder die Instanz benötigt, machen Sie es zu einer Funktion. Es ist selten notwendig, etwas zu einer statischen Methode zu machen, aber wenn Sie feststellen, dass eine Funktion mit einer Klasse "gruppiert" werden soll (z. B. damit sie überschrieben werden kann), obwohl sie keinen Zugriff auf die Klasse benötigt, denke ich Sie könnten es zu einer statischen Methode machen.

Ich würde hinzufügen, dass das Setzen von Funktionen auf Modulebene den Namespace nicht "verschmutzt". Wenn die Funktionen verwendet werden sollen, verschmutzen sie den Namespace nicht, sondern verwenden ihn so, wie er verwendet werden sollte. Funktionen sind legitime Objekte in einem Modul, genau wie Klassen oder irgendetwas anderes. Es gibt keinen Grund, eine Funktion in einer Klasse auszublenden, wenn sie keinen Grund hat, dort zu sein.

BrenBarn
quelle
Als ich über die Verschmutzung des Namespace schrieb, dachte ich mehr an mögliche Namenskonflikte, wenn jemand "aus Foo-Import *" machen sollte. Ich nehme an, es liegt in der Verantwortung jedes Programmierers, darauf zu achten.
Doval
14
Deshalb from Foo import *wird davon abgeraten. Wenn die Funktionen modulintern und nicht Teil der öffentlichen API sein sollen, können Sie sie mit einem führenden Unterstrich benennen und werden dabei nicht importiert from Foo import *.
BrenBarn
4
+1 für If the functions are meant to be used, they're not polluting the namespace, they're using it just as it should be used.Wenn das umweltschädlich ist, warum dann überhaupt einen Namespace haben, wenn Sie ihn nur vermeiden wollen?
Rickcnagy
Toller
ecoe
30

Tolle Antwort von BrenBarn , aber ich würde "Wenn es keinen Zugriff auf die Klasse oder die Instanz benötigt, machen Sie es zu einer Funktion" ändern , um :

‚Wenn es keinen Zugriff auf die Klasse benötigen oder die Instanz ... aber ist thematisch in Bezug auf die Klasse (typisches Beispiel: Hilfsfunktionen und Konvertierungsfunktionen von anderen Klassenmethoden verwendet oder durch alternative Konstrukteuren verwendet wird ), dann verwenden static

sonst mach es a Modulfunktion

smci
quelle
Nun, make_from_XXX()klingt wie eine alternative Konstruktor , so dass sie paßt nicht richtig als static.
Gall
@Gall du hast recht, ich habe diesen Kommentar geändert. Es gibt eine gute Diskussion über die Verwendung statischer Methoden in Python - Best Practice
smci
Statische Methode ist wirklich nur eine kosmetische Sache. Es gibt praktisch keinen Unterschied zwischen einer statischen Methode und einer Funktion, da die statische Methode alle Informationen nur aus Argumenten bezieht, genau wie einfache Funktionen. Sie können jede Funktion als statisch auf eine Klasse kleben oder umgekehrt, und es würde absolut nichts ändern, außer wie Sie sie aufrufen.
1
@hayavuk: Es gibt einen konzeptionellen Unterschied darin, dass wir signalisieren, dass eine statische Methode zu einer bestimmten Klasse gehört und dass Code, Dokumentzeichenfolge, Dokumentation, verwandte Konstanten usw. in denselben Dateien zusammengehalten werden sollten. Wenn wir das nicht getan hätten, betrachten wir zB das datetimeModul: Es würde weniger Hinweise geben, datetime.fromtimestamp()die auf keiner alten Ganzzahl aufgerufen werden sollen.
smci
@smci Ich würde immer noch "wir signalisieren" als kosmetische Sache ablegen. Es ändert nicht wirklich das fromtimestamp()Verhalten. Ich denke, das fromtimestamp()sollte hier wirklich eine Klassenmethode sein, keine statische Methode.
15

Dies ist nicht wirklich eine Antwort, sondern ein langer Kommentar:

Noch rätselhafter ist, dass dieser Code:

        class A:
            def foo(x):
                print(x)
        A.foo(5)

Schlägt in Python 2.7.3 wie erwartet fehl, funktioniert aber in 3.2.3 einwandfrei (obwohl Sie die Methode nicht für eine Instanz von A aufrufen können, sondern nur für die Klasse.)

Ich werde versuchen zu erklären, was hier passiert.

Dies ist streng genommen ein Missbrauch des "normalen" Instanzmethodenprotokolls.

Was Sie hier definieren, ist eine Methode, aber mit dem ersten (und einzigen) Parameter nicht benannt self, sondern x. Natürlich können Sie die Methode in einer Instanz von Aaufrufen, aber Sie müssen sie folgendermaßen aufrufen:

A().foo()

oder

a = A()
a.foo()

Daher wird die Instanz der Funktion als erstes Argument übergeben.

Die Möglichkeit, reguläre Methoden über die Klasse aufzurufen, war schon immer vorhanden und funktioniert von

a = A()
A.foo(a)

Wenn Sie hier die Methode der Klasse und nicht die Instanz aufrufen, wird der erste Parameter nicht automatisch angegeben, sondern Sie müssen ihn angeben.

Solange dies eine Instanz von ist A , ist alles in Ordnung. Etwas anderes zu geben ist IMO ein Missbrauch des Protokolls und damit der Unterschied zwischen Py2 und Py3:

In Py2 A.foo in eine ungebundene Methode umgewandelt und erfordert daher, dass das erste Argument eine Instanz der Klasse ist, in der es "lebt". Das Aufrufen mit etwas anderem schlägt fehl.

In Py3 wurde diese Prüfung gelöscht und A.fooist nur das ursprüngliche Funktionsobjekt. Sie können es also mit allem als erstem Argument bezeichnen, aber ich würde es nicht tun. Der erste Parameter einer Methode sollte immer benannt sein selfund die Semantik von haben self.

glglgl
quelle
0

Die beste Antwort hängt davon ab, wie die Funktion verwendet wird. In meinem Fall schreibe ich Anwendungspakete, die in Jupyter-Notizbüchern verwendet werden. Mein Hauptziel ist es, dem Benutzer die Sache zu erleichtern.

Der Hauptvorteil von Funktionsdefinitionen besteht darin, dass der Benutzer seine definierende Datei mit dem Schlüsselwort "as" importieren kann. Auf diese Weise kann der Benutzer die Funktionen auf dieselbe Weise aufrufen wie eine Funktion in numpy oder matplotlib.

Einer der Nachteile von Python ist, dass Namen nicht gegen weitere Zuweisungen geschützt werden können. Wenn jedoch "import numpy as np" oben im Notizbuch angezeigt wird, ist dies ein starker Hinweis darauf, dass "np" nicht als allgemeiner Variablenname verwendet werden sollte. Sie können das Gleiche natürlich mit Klassennamen erreichen, aber die Vertrautheit der Benutzer ist sehr wichtig.

In den Paketen bevorzuge ich jedoch statische Methoden. Meine Softwarearchitektur ist objektorientiert und ich schreibe mit Eclipse, das ich für mehrere Zielsprachen verwende. Es ist praktisch, die Quelldatei zu öffnen und die Klassendefinition auf der obersten Ebene, die auf einer Ebene eingerückten Methodendefinitionen usw. anzuzeigen. Die Zielgruppe für den Code auf dieser Ebene sind hauptsächlich andere Analysten und Entwickler. Daher ist es besser, sprachspezifische Redewendungen zu vermeiden.

Ich habe nicht viel Vertrauen in die Python-Namespace-Verwaltung, insbesondere wenn Entwurfsmuster verwendet werden, bei denen (sagen wir) ein Objekt einen Verweis auf sich selbst übergibt, damit das aufgerufene Objekt eine im Aufrufer definierte Methode aufrufen kann. Also versuche ich es nicht zu weit zu zwingen. Ich verwende viele vollqualifizierte Namen und explizite Instanzvariablen (mit self ), bei denen ich mich in anderen Sprachen darauf verlassen kann, dass der Interpreter oder der Compiler den Bereich genauer verwaltet. Mit Klassen und statischen Methoden ist dies einfacher. Aus diesem Grund sind sie meiner Meinung nach die bessere Wahl für komplexe Pakete, bei denen Abstraktion und Verstecken von Informationen am nützlichsten sind.

user5920660
quelle