Wann sollte ich in Python eine Funktion anstelle einer Methode verwenden?

Das Zen von Python gibt an, dass es nur eine Möglichkeit geben sollte, Dinge zu tun. Dennoch stoße ich häufig auf das Problem, zu entscheiden, wann eine Funktion verwendet werden soll und wann eine Methode verwendet werden soll.

Nehmen wir ein triviales Beispiel - ein ChessBoard-Objekt. Nehmen wir an, wir brauchen einen Weg, um alle legalen Königsmoves verfügbar zu machen. Schreiben wir ChessBoard.get_king_moves() oder get_king_moves (chess_board)?

Hier sind einige verwandte Fragen, die ich mir angesehen habe:

Die Antworten, die ich erhielt, waren größtenteils nicht eindeutig:

Warum verwendet Python Methoden für einige Funktionen (z. B. list.index ()), funktioniert aber für andere (z. B. len (list))?

     

Der Hauptgrund ist Geschichte. Funktionen wurden für diejenigen Operationen verwendet, die für eine Gruppe von Typen generisch waren und die waren   beabsichtigt, auch für Objekte zu arbeiten, die überhaupt keine Methoden haben   (z.B. Tupel). Es ist auch bequem, eine Funktion zu haben, die kann   bereitwillig auf eine amorphe Sammlung von Objekten angewendet werden, wenn Sie verwenden   die funktionalen Eigenschaften von Python (map (), apply() ua).

     

Tatsächlich ist das Implementieren von len (), max (), min() als eine eingebaute Funktion tatsächlich weniger Code, als sie als Methoden für jeden Typ zu implementieren.   Man kann über einzelne Fälle streiten, aber es ist ein Teil von Python, und   Es ist zu spät, um jetzt so grundlegende Änderungen vorzunehmen. Die Funktionen haben   zu bleiben, um massiven Code-Bruch zu vermeiden.

Obwohl interessant, sagt das Obige nicht viel darüber aus, welche Strategie zu übernehmen ist.

Dies ist einer der Gründe - mit benutzerdefinierten Methoden würden Entwickler sein   frei, einen anderen Methodennamen zu wählen, wie getLength (), length (),   Geduld() oder was auch immer. Python erzwingt strikte Namensgebung, so dass der   Gemeinsame Funktion len() kann verwendet werden.

Etwas interessanter. Ich nehme an, dass Funktionen in gewissem Sinne die pythonische Version von Interfaces sind.

Zuletzt, von Guido selbst :

Talking about the Abilities/Interfaces made me think about some of our "rogue" special method names. In the Language Reference, it says, "A class can implement certain operations that are invoked by special syntax (such as arithmetic operations or subscripting and slicing) by defining methods with special names." But there are all these methods with special names like __len__ or __unicode__ which seem to be provided for the benefit of built-in functions, rather than for support of syntax. Presumably in an interface-based Python, these methods would turn into regularly-named methods on an ABC, so that __len__ would become

class container:
  ...
  def len(self):
    raise NotImplemented

Though, thinking about it some more, I don't see why all syntactic operations wouldn't just invoke the appropriate normally-named method on a specific ABC. "<", for instance, would presumably invoke "object.lessthan" (or perhaps "comparable.lessthan"). So another benefit would be the ability to wean Python away from this mangled-name oddness, which seems to me an HCI improvement.

Hm. I'm not sure I agree (figure that :-).

There are two bits of "Python rationale" that I'd like to explain first.

First of all, I chose len(x) over x.len() for HCI reasons (def __len__() came much later). There are two intertwined reasons actually, both HCI:

(a) For some operations, prefix notation just reads better than postfix -- prefix (and infix!) operations have a long tradition in mathematics which likes notations where the visuals help the mathematician thinking about a problem. Compare the easy with which we rewrite a formula like x*(a+b) into x*a + x*b to the clumsiness of doing the same thing using a raw OO notation.

(b) When I read code that says len(x) I know that it is asking for the length of something. This tells me two things: the result is an integer, and the argument is some kind of container. To the contrary, when I read x.len(), I have to already know that x is some kind of container implementing an interface or inheriting from a class that has a standard len(). Witness the confusion we occasionally have when a class that is not implementing a mapping has a get() or keys() method, or something that isn't a file has a write() method.

Saying the same thing in another way, I see 'len' as a built-in operation. I'd hate to lose that. I can't say for sure whether you meant that or not, but 'def len(self): ...' certainly sounds like you want to demote it to an ordinary method. I'm strongly -1 on that.

The second bit of Python rationale I promised to explain is the reason why I chose special methods to look __special__ and not merely special. I was anticipating lots of operations that classes might want to override, some standard (e.g. __add__ or __getitem__), some not so standard (e.g. pickle's __reduce__ for a long time had no support in C code at all). I didn't want these special operations to use ordinary method names, because then pre-existing classes, or classes written by users without an encyclopedic memory for all the special methods, would be liable to accidentally define operations they didn't mean to implement, with possibly disastrous consequences. Ivan Krstić explained this more concise in his message, which arrived after I'd written all this up.

-- --Guido van Rossum (home page: http://www.python.org/~guido/)

Mein Verständnis davon ist, dass in bestimmten Fällen Präfix-Notation nur mehr Sinn macht (dh Duck.quack macht mehr Sinn als Quack (Duck) vom linguistischen Standpunkt aus.) Und wieder erlauben die Funktionen "Interfaces".

In einem solchen Fall würde ich annehmen, dass get_king_moves ausschließlich auf Guidos erstem Punkt basiert. Aber das lässt noch viele offene Fragen offen, zum Beispiel die Implementierung einer Stack- und Queue-Klasse mit ähnlichen Push- und Pop-Methoden - sollten es Funktionen oder Methoden sein? (hier würde ich Funktionen raten, weil ich wirklich eine Push-Pop-Schnittstelle signalisieren möchte)

TLDR: Kann jemand erklären, was die Strategie sein sollte, um zu entscheiden, wann man Funktionen gegen Methoden verwenden sollte?

88
Während ich größtenteils mit Ihnen einverstanden bin, ist Ihre Antwort im Prinzip nicht Pythonic. Erinnern Sie sich: "Angesichts der Zweideutigkeit lehnen Sie die Versuchung ab, zu raten." (Natürlich würden sich Deadlines ändern, aber ich mache das aus Spaß/Selbstverbesserung.)
hinzugefügt der Autor Ceasar Bautista, Quelle
Das ist eine Sache, die ich an Python nicht mag. Ich habe das Gefühl, wenn Sie die Cast-Eingabe wie einen Int-String erzwingen wollen, dann machen Sie es einfach zur Methode. Es ist ärgerlich, es in Parens und zeitraubend umschließen zu müssen.
hinzugefügt der Autor Matt, Quelle
Dies ist der wichtigste Grund, warum ich Python nicht mag: Man weiß nie, ob man nach einer Funktion oder einer Methode suchen muss, um etwas zu erreichen. Und es wird sogar noch komplizierter, wenn Sie zusätzliche Bibliotheken mit neuen Datentypen wie Vektoren oder Datenrahmen verwenden.
hinzugefügt der Autor vonjd, Quelle
Meh, das habe ich immer als absolut willkürlich empfunden. Duck Typisierung ermöglicht implizite "Schnittstellen", es macht keinen großen Unterschied, ob Sie X.frob oder X .__ frob __ und freistehenden frob .
hinzugefügt der Autor Cat Plus Plus, Quelle
"Das Zen von Python besagt, dass es nur einen Weg geben sollte, etwas zu tun" , außer es nicht.
hinzugefügt der Autor gented, Quelle

6 Antworten

Meine allgemeine Regel ist dies - ist die Operation, die für das Objekt oder das Objekt ausgeführt wird?

Wenn es von dem Objekt ausgeführt wird, sollte es eine Elementoperation sein. Wenn es sich auch auf andere Dinge beziehen könnte oder durch etwas anderes für das Objekt, dann sollte es eine Funktion (oder vielleicht ein Mitglied von etwas anderem) sein.

Bei der Einführung der Programmierung ist es traditionell (wenn auch in der Implementierung nicht korrekt) Objekte in realen Objekten wie Autos zu beschreiben. Du erwähnst eine Ente, also lass uns damit gehen.

class duck: 
    def __init__(self):pass
    def eat(self, o): pass 
    def crap(self) : pass
    def die(self)
    ....

Im Zusammenhang mit der Analogie "Objekte sind reale Dinge" ist es "richtig", für alles, was das Objekt kann, eine Klassenmethode hinzuzufügen. Also sag ich will eine Ente töten, füge ich ein .kill() zur Ente? Nein ... Soweit ich weiß, begehen Tiere keinen Selbstmord. Wenn ich also eine Ente töten möchte, sollte ich das tun:

def kill(o):
    if isinstance(o, duck):
        o.die()
    elif isinstance(o, dog):
        print "WHY????"
        o.die()
    elif isinstance(o, nyancat):
        raise Exception("NYAN "*9001)
    else:
       print "can't kill it."

Ziehen wir uns von dieser Analogie weg, warum verwenden wir Methoden und Klassen? Weil wir Daten enthalten und unseren Code hoffentlich so strukturieren wollen, dass er in Zukunft wiederverwendbar und erweiterbar ist. Dies bringt uns zu der Idee der Verkapselung, die dem OO-Design so wichtig ist.

Das Prinzip der Kapselung ist wirklich, worauf es ankommt: als Designer sollte man alles über die Implementierung und die Klasseninterna verbergen, auf die es nicht unbedingt unbedingt für jeden Benutzer oder anderen Entwickler zugreifen muss. Da wir uns mit Instanzen von Klassen befassen, reduziert sich dies auf "welche Vorgänge von entscheidender Bedeutung sind für diese Instanz ". Wenn eine Operation nicht instanzspezifisch ist, sollte sie keine Elementfunktion sein.

TL;DR: what @Bryan said. If it operates on an instance and needs to access data which is internal to the class instance, it should be a member function.

63
hinzugefügt
Kurz gesagt, arbeiten Nichtmitgliedsfunktionen auf unveränderlichen Objekten, veränderbare Objekte auf Mitgliedsfunktionen? (Oder ist das eine zu strenge Verallgemeinerung? Dies für bestimmte Werke nur, weil unveränderliche Typen keinen Zustand haben.)
hinzugefügt der Autor Ceasar Bautista, Quelle
essen (Selbst), Mist (Selbst), sterben (Selbst). hahahaha
hinzugefügt der Autor Wapiti, Quelle
Entschuldigung, ich denke du hast den Punkt verpasst. Es geht um Modellierung und Semantik und was natürlicher und/oder nützlicher klingt.
hinzugefügt der Autor Karl Knechtel, Quelle
@ Ceasar Das ist nicht wahr. Unveränderliche Objekte haben einen Zustand; Sonst gäbe es nichts, was eine ganze Zahl von anderen unterscheiden könnte. Unveränderbare Objekte ändern ihren Zustand nicht. Im Allgemeinen ist es dadurch für alle Staaten viel weniger problematisch, öffentlich zu sein. Und in dieser Situation ist es viel einfacher, ein unveränderliches öffentliches Objekt mit Funktionen sinnvoll zu manipulieren; Es gibt kein "Privileg", eine Methode zu sein.
hinzugefügt der Autor Ben, Quelle
Das Schlüsselwort is prüft nicht den Typ, sondern die Identität.
hinzugefügt der Autor les, Quelle
@ CeasarBautista ja, Ben hat einen Punkt. Es gibt drei "große" Schulen für Code-Design 1) nicht entworfen, 2) OOP und 3) funktional. In einem funktionalen Stil gibt es überhaupt keine Zustände. Auf diese Weise sehe ich den meisten Python-Code entworfen, er nimmt Input und generiert Output mit wenigen Nebenwirkungen. Der Punkt von OOP ist, dass alles einen Zustand hat. Klassen sind ein Container für Zustände, und "Member" -Funktionen sind daher zustandsabhängiger und auf Seiteneffekten basierender Code, der den in der Klasse definierten Zustand lädt, wenn er aufgerufen wird. Python neigt dazu, funktional zu lehnen, daher die Präferenz von Nicht-Mitglieder-Code.
hinzugefügt der Autor arrdem, Quelle
Von einem strengen OOP-Standpunkt aus denke ich, dass das fair ist. Da Python sowohl öffentliche als auch private Variablen (Variablen mit Namen, die in __ beginnen) besitzt und im Gegensatz zu Java keinen garantierten Zugriffsschutz bietet, gibt es keine absoluten Werte, weil wir eine permissive Sprache diskutieren. In einer weniger permissiven Sprache wie Java jedoch, erinnere dich daran, dass getFoo() ad setFoo() Funktionen die Norm ist, also ist die Unveränderlichkeit nicht absolut. Der Client-Code darf nur keine Zuweisungen an Mitglieder vornehmen.
hinzugefügt der Autor arrdem, Quelle

Verwenden Sie eine Klasse, wenn Sie:

1) Isolieren Sie den Aufrufcode von den Implementierungsdetails - nutzen Sie die Abstraktion und Kapselung .

2) Wenn Sie für andere Objekte substituierbar sein möchten - nutzen Sie Polymorphismus .

3) Wenn Sie Code für ähnliche Objekte wiederverwenden möchten - nutzen Sie die Vererbung .

Verwenden Sie eine Funktion für Aufrufe, die für viele verschiedene Objekttypen sinnvoll sind - zum Beispiel das eingebaute len und repr Funktionen gelten für viele Arten von Objekten.

Davon abgesehen kommt es manchmal auf eine Frage des Geschmacks an. Denken Sie darüber nach, was für typische Anrufe am bequemsten und lesbar ist. Zum Beispiel wäre das besser (x.sin() ** 2 + y.cos() ** 2) .sqrt() oder sqrt (sin (x) ** 2) + cos (y) ** 2) ?

23
hinzugefügt

Hier ist eine einfache Faustregel: Wenn der Code auf eine einzelne Instanz eines Objekts wirkt, verwenden Sie eine Methode. Noch besser: Verwenden Sie eine Methode, es sei denn, es gibt einen zwingenden Grund, sie als Funktion zu schreiben.

In Ihrem speziellen Beispiel soll es so aussehen:

chessboard = Chessboard()
...
chessboard.get_king_moves()

Überlege es dir nicht. Benutze immer Methoden, bis der Punkt kommt, wo du zu dir selbst sagst "es macht keinen Sinn, dies zu einer Methode zu machen", in diesem Fall kannst du eine Funktion machen.

6
hinzugefügt
Können Sie erklären, warum Sie Methoden gegenüber Funktionen standardmäßig verwenden? (Und könnten Sie erklären, ob diese Regel bei den Stack - und Queue/Pop und Push - Methoden noch Sinn macht?)
hinzugefügt der Autor Ceasar Bautista, Quelle
Nun, die STL hat einige gute Gründe dafür. Für Mathe und Co ist es offensichtlich nutzlos, eine Klasse zu haben, da wir keinen Zustand dafür haben (in anderen Sprachen sind dies Klassen mit nur statischen Funktionen). Für Dinge, die auf verschiedenen Containern laufen, wie zB len() , kann man auch argumentieren, dass das Design sinnvoll ist, obwohl ich persönlich auch denken würde, dass Funktionen dort auch nicht so schlecht wären - wir würden habe einfach eine andere Konvention, dass len() immer eine ganze Zahl zurückgeben muss (aber mit all den zusätzlichen Problemen von backcomp würde ich das auch nicht für Python befürworten)
hinzugefügt der Autor Voo, Quelle
Wollen Sie sagen, dass die Regel keinen Sinn ergibt, weil die Standardbibliothek dies verletzt? Ich denke nicht, dass diese Schlussfolgerung sinnvoll ist. Zum einen sind Faustregeln genau das - sie sind keine absoluten Regeln. Außerdem folgt ein großer Teil der Standardbibliothek dieser Faustregel. Das OP war auf der Suche nach ein paar einfachen Richtlinien, und ich denke, mein Rat ist perfekt für jemanden, der gerade erst anfängt. Ich schätze Ihren Standpunkt jedoch.
hinzugefügt der Autor Bryan Oakley, Quelle
Diese Faustregel macht keinen Sinn. Die Standardbibliothek selbst kann ein Hinweis darauf sein, wann Klassen gegen Funktionen verwendet werden. heapq und math sind Funktionen, da sie mit normalen Python-Objekten (Gleitkommazahlen und Listen) arbeiten und keinen komplexen Zustand wie random Modul tut es.
hinzugefügt der Autor Raymond Hettinger, Quelle
-1 da diese Antwort völlig willkürlich ist: Sie versuchen im Wesentlichen zu beweisen, dass X besser ist als Y, indem Sie annehmen, dass X besser als Y ist.
hinzugefügt der Autor gented, Quelle

Normalerweise denke ich an ein Objekt wie eine Person.

Attributes are the person's name, height, shoe size, etc.

Methods and functions are operations that the person can perform.

Wenn die Operation nur von einer beliebigen Person durchgeführt werden kann, ohne etwas für diese eine bestimmte Person einzigartig zu machen (und ohne irgendetwas an dieser einen spezifischen Person zu ändern), dann ist es eine Funktion und sollte als geschrieben werden eine solche.

Wenn eine Operation ist, die auf die Person wirkt (zB essen, gehen, ...) oder erfordert etwas Einzigartiges um sich einzubringen (wie Tanzen, ein Buch schreiben, ...), dann sollte es eine Methode sein.

Natürlich ist es nicht immer trivial, dies in das spezifische Objekt zu übersetzen, mit dem Sie arbeiten, aber ich finde, es ist ein guter Weg, darüber nachzudenken.

6
hinzugefügt
Dies trifft nicht zu. Was ist, wenn Sie are_married (person1, person2) haben? Diese Abfrage ist sehr allgemein und sollte daher eine Funktion und keine Methode sein.
hinzugefügt der Autor Pithikos, Quelle
aber Sie können die Höhe einer alten Person messen, also sollte sie nach dieser Logik height (person) sein, nicht person.height ?
hinzugefügt der Autor endolith, Quelle
@Pithikos Sicher, aber das beinhaltet auch mehr als nur ein Attribut oder eine Aktion, die an einem Objekt (Person) ausgeführt wird, sondern eher eine Beziehung zwischen zwei Objekten.
hinzugefügt der Autor Thriveth, Quelle
@endolith Andererseits, wenn die Person ein Kind ist, das wächst und die Körpergröße ändert, wäre es offensichtlich, eine Methode dafür sorgen zu lassen, nicht eine Funktion.
hinzugefügt der Autor Thriveth, Quelle
@endolith Sicher, aber ich würde sagen, die Höhe ist besser als Attribut, weil Sie keine ausgefallene Arbeit ausführen müssen, um es zu erhalten. Das Schreiben einer Funktion zum Abrufen einer Zahl scheint wie ein unnötiger Umweg zu sein.
hinzugefügt der Autor Thriveth, Quelle

Im Allgemeinen verwende ich Klassen, um einen logischen Satz von Fähigkeiten für einige Dinge zu implementieren, so dass ich im Rest meines Programms über die Sache nachdenken kann, ohne sich um alles kümmern zu müssen die kleinen Sorgen, die seine Umsetzung ausmachen.

Alles, was Teil dieser Kernabstraktion von "Was Sie mit einer Sache tun können, sollte normalerweise eine Methode sein. Dies umfasst im Allgemeinen alles, was eine Sache verändern kann, da der interne Datenzustand normalerweise als privat betrachtet wird und nicht Teil der logischen Idee "Was Sie mit einer Sache tun können" .

Wenn Sie zu Operationen auf höherer Ebene kommen, insbesondere wenn sie mehrere Dinge beinhalten, werden sie normalerweise am natürlichsten als Funktionen ausgedrückt, wenn sie aus der öffentlichen Abstraktion einer Sache ohne besonderen Zugriff auf die Interna (außer sie sind Methoden eines anderen Objekts). Das hat den großen Vorteil, dass ich, wenn ich beschließe, die Interna komplett neu zu schreiben, wie meine Sache funktioniert (ohne die Schnittstelle zu ändern), habe ich nur eine kleine Kernmenge von Methoden umzuschreiben, und dann alle externen Funktionen, die in Bezug auf diese Methoden geschrieben wurden, funktionieren einfach. Ich finde, dass das Beharren, dass alle Operationen, die mit Klasse X zu tun sind, Methoden in Klasse X sind, zu übermäßig komplizierten Klassen führt.

Es hängt jedoch vom Code ab, den ich schreibe. Für einige Programme modelliere ich sie als eine Sammlung von Objekten, deren Interaktionen das Verhalten des Programms hervorrufen; Hier ist die wichtigste Funktionalität eng an ein einzelnes Objekt gekoppelt und wird so in Methoden implementiert, die eine Vielzahl von Nutzenfunktionen enthalten. Für andere Programme ist das Wichtigste eine Reihe von Funktionen, die Daten manipulieren, und Klassen werden nur verwendet, um die natürlichen "Ducktypen" zu implementieren, die von den Funktionen manipuliert werden.

4
hinzugefügt

Sie können sagen, dass "angesichts der Ambiguität, lehnen Sie die Versuchung zu erraten".

Es ist jedoch nicht einmal eine Vermutung. Sie sind absolut sicher, dass die Ergebnisse beider Ansätze die gleichen sind, da sie Ihr Problem lösen.

Ich glaube, es ist nur eine gute Sache, mehrere Möglichkeiten zu haben, Ziele zu erreichen. Ich würde demütig sagen, wie andere Benutzer es bereits getan haben, was auch immer "besser schmeckt"/fühlt sich mehr intuitiv in Bezug auf die Sprache.

0
hinzugefügt