Wann sollte ich Klassen in Python verwenden?

173

Ich programmiere seit ungefähr zwei Jahren in Python. Meistens Daten (Pandas, MPL, Numpy), aber auch Automatisierungsskripte und kleine Web-Apps. Ich versuche, ein besserer Programmierer zu werden und mein Python-Wissen zu erweitern. Eines der Dinge, die mich stören, ist, dass ich noch nie eine Klasse verwendet habe (außerhalb des Kopierens von zufälligem Flask-Code für kleine Web-Apps). Ich verstehe im Allgemeinen, was sie sind, aber ich kann mich nicht darum kümmern, warum ich sie für eine einfache Funktion benötigen würde.

Um meiner Frage mehr Spezifität zu verleihen: Ich schreibe Tonnen von automatisierten Berichten, bei denen immer Daten aus mehreren Datenquellen (Mongo, SQL, Postgres, API) abgerufen, viele oder wenige Daten mungiert und formatiert und die Daten in csv / excel geschrieben werden / html, senden Sie es in einer E-Mail. Die Skripte reichen von ~ 250 Zeilen bis ~ 600 Zeilen. Gibt es einen Grund für mich, dafür Klassen zu verwenden und warum?

meterk
quelle
15
Es ist nichts Falsches daran, ohne Klassen zu codieren, wenn Sie Ihren Code besser verwalten können. OOP-Programmierer neigen dazu, die Probleme aufgrund der Einschränkungen des Sprachdesigns oder des oberflächlichen Verständnisses verschiedener Muster zu übertreiben.
Jason Hu

Antworten:

130

Klassen sind die Säule der objektorientierten Programmierung . OOP befasst sich stark mit der Organisation, Wiederverwendbarkeit und Kapselung von Code.

Erstens ein Haftungsausschluss: OOP steht teilweise im Gegensatz zu Functional Programming , einem anderen Paradigma, das in Python häufig verwendet wird. Nicht jeder, der in Python (oder sicherlich in den meisten Sprachen) programmiert, verwendet OOP. In Java 8 können Sie viel tun, was nicht sehr objektorientiert ist. Wenn Sie OOP nicht verwenden möchten, tun Sie dies nicht. Wenn Sie nur einmalige Skripte schreiben, um Daten zu verarbeiten, die Sie nie wieder verwenden werden, schreiben Sie weiter so, wie Sie sind.

Es gibt jedoch viele Gründe, OOP zu verwenden.

Einige Gründe:

  • Organisation: OOP definiert bekannte und standardmäßige Methoden zur Beschreibung und Definition von Daten und Prozeduren im Code. Sowohl Daten als auch Prozeduren können auf verschiedenen Definitionsebenen (in verschiedenen Klassen) gespeichert werden, und es gibt Standardmethoden, um über diese Definitionen zu sprechen. Das heißt, wenn Sie OOP auf standardmäßige Weise verwenden, hilft dies Ihrem späteren Selbst und anderen, Ihren Code zu verstehen, zu bearbeiten und zu verwenden. Anstatt einen komplexen, willkürlichen Datenspeicherungsmechanismus zu verwenden (Diktate von Diktaten oder Listen oder Diktate oder Listen von Diktaten von Mengen oder was auch immer), können Sie auch Teile von Datenstrukturen benennen und bequem darauf verweisen.

  • Status: Mit OOP können Sie den Status definieren und verfolgen. Wenn Sie beispielsweise in einem klassischen Beispiel ein Programm erstellen, das Schüler verarbeitet (z. B. ein Notenprogramm), können Sie alle Informationen, die Sie über sie benötigen, an einem Ort aufbewahren (Name, Alter, Geschlecht, Klassenstufe, Kurse, Noten, Lehrer, Gleichaltrige, Ernährung, besondere Bedürfnisse usw.), und diese Daten bleiben erhalten, solange das Objekt lebt und leicht zugänglich ist.

  • Kapselung : Bei der Kapselung werden Prozedur und Daten zusammen gespeichert. Methoden (ein OOP-Begriff für Funktionen) werden direkt neben den Daten definiert, mit denen sie arbeiten und die sie erzeugen. In einer Sprache wie Java, die Zugriffskontrolle ermöglicht , oder in Python, je nachdem, wie Sie Ihre öffentliche API beschreiben, bedeutet dies, dass Methoden und Daten vor dem Benutzer verborgen werden können. Dies bedeutet, dass Sie, wenn Sie Code benötigen oder ändern möchten, alles tun können, um den Code zu implementieren, aber die öffentlichen APIs gleich lassen.

  • Vererbung : Mit Vererbung können Sie Daten und Prozeduren an einem Ort (in einer Klasse) definieren und diese Funktionalität später überschreiben oder erweitern. In Python sehe ich beispielsweise häufig Leute, die Unterklassen der dictKlasse erstellen , um zusätzliche Funktionen hinzuzufügen. Eine häufige Änderung besteht darin, die Methode zu überschreiben, die eine Ausnahme auslöst, wenn ein Schlüssel aus einem nicht vorhandenen Wörterbuch angefordert wird, um einen Standardwert basierend auf einem unbekannten Schlüssel anzugeben. Auf diese Weise können Sie Ihren eigenen Code jetzt oder später erweitern, anderen erlauben, Ihren Code zu erweitern, und Sie können den Code anderer Personen erweitern.

  • Wiederverwendbarkeit: Alle diese und andere Gründe ermöglichen eine bessere Wiederverwendbarkeit von Code. Mit objektorientiertem Code können Sie festen (getesteten) Code einmal schreiben und dann immer wieder verwenden. Wenn Sie etwas für Ihren speziellen Anwendungsfall optimieren müssen, können Sie von einer vorhandenen Klasse erben und das vorhandene Verhalten überschreiben. Wenn Sie etwas ändern müssen, können Sie alles ändern, während Sie die vorhandenen öffentlichen Methodensignaturen beibehalten, und niemand ist (hoffentlich) klüger.

Auch hier gibt es mehrere Gründe, OOP nicht zu verwenden, und Sie müssen es nicht. Aber zum Glück können Sie mit einer Sprache wie Python nur ein bisschen oder viel verwenden, es liegt an Ihnen.

Ein Beispiel für den Anwendungsfall eines Schülers (keine Garantie für die Codequalität, nur ein Beispiel):

Objektorientierte

class Student(object):
    def __init__(self, name, age, gender, level, grades=None):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level
        self.grades = grades or {}

    def setGrade(self, course, grade):
        self.grades[course] = grade

    def getGrade(self, course):
        return self.grades[course]

    def getGPA(self):
        return sum(self.grades.values())/len(self.grades)

# Define some students
john = Student("John", 12, "male", 6, {"math":3.3})
jane = Student("Jane", 12, "female", 6, {"math":3.5})

# Now we can get to the grades easily
print(john.getGPA())
print(jane.getGPA())

Standard Dikt

def calculateGPA(gradeDict):
    return sum(gradeDict.values())/len(gradeDict)

students = {}
# We can set the keys to variables so we might minimize typos
name, age, gender, level, grades = "name", "age", "gender", "level", "grades"
john, jane = "john", "jane"
math = "math"
students[john] = {}
students[john][age] = 12
students[john][gender] = "male"
students[john][level] = 6
students[john][grades] = {math:3.3}

students[jane] = {}
students[jane][age] = 12
students[jane][gender] = "female"
students[jane][level] = 6
students[jane][grades] = {math:3.5}

# At this point, we need to remember who the students are and where the grades are stored. Not a huge deal, but avoided by OOP.
print(calculateGPA(students[john][grades]))
print(calculateGPA(students[jane][grades]))
Dantiston
quelle
Aufgrund der "Ausbeute" ist die Python-Kapselung bei Generatoren und Kontextmanagern häufig sauberer als bei Klassen.
Dmitry Rubanovich
4
@meter Ich habe ein Beispiel hinzugefügt. Ich hoffe, es hilft. Der Hinweis hier ist, dass sich der Python-Interpreter nicht auf die Schlüssel Ihrer Diktate mit dem richtigen Namen verlassen muss, sondern diese Einschränkung für Sie vornimmt, wenn Sie Fehler machen und Sie zwingen, definierte Methoden zu verwenden (obwohl keine definierten Felder (obwohl Java und andere) In OOP-Sprachen können Sie keine Felder außerhalb von Klassen wie Python definieren.
Dantiston
5
@meter auch als Beispiel für die Kapselung: Nehmen wir an, diese Implementierung ist heute in Ordnung, da ich den GPA für 50.000 Studenten an meiner Universität nur einmal pro Semester erhalten muss. Jetzt bekommen wir morgen ein Stipendium und müssen jede Sekunde den aktuellen GPA jedes Schülers vergeben (natürlich würde niemand danach fragen, aber nur, um es rechnerisch herausfordernd zu machen). Wir könnten dann den GPA "auswendig lernen" und ihn nur berechnen, wenn er sich ändert (z. B. durch Festlegen einer Variablen in der setGrade-Methode), während andere eine zwischengespeicherte Version zurückgeben. Der Benutzer verwendet weiterhin getGPA (), aber die Implementierung hat sich geändert.
Dantiston
4
@dantiston, dieses Beispiel benötigt collection.namedtuple. Sie können einen neuen Typ Student = collection.namedtuple ("Student", "Name, Alter, Geschlecht, Stufe, Noten") erstellen. Und dann können Sie Instanzen erstellen john = Student ("John", 12, "männlich", Noten = {'math': 3.5}, Stufe = 6). Beachten Sie, dass Sie sowohl positionelle als auch benannte Argumente verwenden, genau wie beim Erstellen einer Klasse. Dies ist ein Datentyp, der bereits in Python für Sie implementiert ist. Sie können dann auf john [0] oder john.name verweisen, um das erste Element des Tupels zu erhalten. Sie können Johns Noten jetzt als john.grades.values ​​() erhalten. Und es ist schon für dich erledigt.
Dmitry Rubanovich
2
Für mich ist die Kapselung ein guter Grund, immer OOP zu verwenden. Ich habe Schwierigkeiten zu sehen, dass der Wert OOP NICHT für ein Codierungsprojekt mit angemessener Größe verwendet. Ich denke, ich brauche Antworten auf die umgekehrte Frage :)
San Jay
23

Wann immer Sie einen Zustand Ihrer Funktionen beibehalten müssen und dies nicht mit Generatoren erreicht werden kann (Funktionen, die eher nachgeben als zurückkehren). Generatoren behalten ihren eigenen Zustand bei.

Wenn Sie einen der Standardoperatoren überschreiben möchten , benötigen Sie eine Klasse.

Wann immer Sie ein Besuchermuster verwenden, benötigen Sie Klassen. Jedes andere Entwurfsmuster kann mit Generatoren, Kontextmanagern (die auch besser als Generatoren als als Klassen implementiert sind) und POD-Typen (Wörterbücher, Listen und Tupel usw.) effektiver und sauberer ausgeführt werden.

Wenn Sie "pythonischen" Code schreiben möchten, sollten Sie Kontextmanager und Generatoren Klassen vorziehen. Es wird sauberer sein.

Wenn Sie die Funktionalität erweitern möchten, können Sie dies fast immer eher mit Eindämmung als mit Vererbung erreichen.

Dies hat in der Regel eine Ausnahme. Wenn Sie die Funktionalität schnell kapseln möchten (dh Testcode anstelle von wiederverwendbarem Code auf Bibliotheksebene schreiben möchten), können Sie den Status in einer Klasse kapseln. Es ist einfach und muss nicht wiederverwendbar sein.

Wenn Sie einen Destruktor im C ++ - Stil (RIIA) benötigen, möchten Sie auf keinen Fall Klassen verwenden. Sie möchten Kontextmanager.

Dmitry Rubanovich
quelle
1
@ DmitryRubanovich-Verschlüsse werden in Python nicht über Generatoren implementiert.
Eli Korvigo
1
@DmitryRubanovich Ich bezog mich auf "Abschlüsse werden als Generatoren in Python implementiert", was nicht wahr ist. Verschlüsse sind weitaus flexibler. Generatoren müssen eine GeneratorInstanz (einen speziellen Iterator) zurückgeben, während Schließungen eine beliebige Signatur haben können. Grundsätzlich können Sie Klassen die meiste Zeit vermeiden, indem Sie Abschlüsse erstellen. Und Verschlüsse sind nicht nur "Funktionen, die im Kontext anderer Funktionen definiert sind".
Eli Korvigo
3
@ Eli Korvigo, in der Tat sind Generatoren syntaktisch ein bedeutender Sprung. Sie erstellen eine Abstraktion einer Warteschlange auf dieselbe Weise, wie Funktionen Abstraktionen eines Stapels sind. Und der größte Teil des Datenflusses kann aus den Stapel- / Warteschlangenprimitiven zusammengesetzt werden.
Dmitry Rubanovich
1
@DmitryRubanovich wir reden hier über Äpfel und Orangen. Ich sage, dass Generatoren in einer sehr begrenzten Anzahl von Fällen nützlich sind und in keiner Weise als Ersatz für allgemeine Stateful Callables angesehen werden können. Sie sagen mir, wie großartig sie sind, ohne meinen Punkten zu widersprechen.
Eli Korvigo
1
@ Eli Korvigo, und ich sage, dass Callables nur Verallgemeinerungen von Funktionen sind. Was selbst syntaktischer Zucker gegenüber der Verarbeitung von Stapeln ist. Während Generatoren syntaktischer Zucker über die Verarbeitung von Warteschlangen sind. Es ist jedoch diese Verbesserung der Syntax, die es ermöglicht, kompliziertere Konstrukte einfach und mit klarerer Syntax aufzubauen. '.next ()' wird übrigens fast nie verwendet.
Dmitry Rubanovich
11

Ich denke du machst es richtig. Klassen sind sinnvoll, wenn Sie eine Geschäftslogik oder schwierige reale Prozesse mit schwierigen Beziehungen simulieren müssen. Zum Beispiel:

  • Mehrere Funktionen mit Freigabestatus
  • Mehr als eine Kopie derselben Statusvariablen
  • So erweitern Sie das Verhalten einer vorhandenen Funktionalität

Ich empfehle Ihnen auch, dieses klassische Video anzusehen

valignatev
quelle
3
Es ist nicht erforderlich, eine Klasse zu verwenden, wenn eine Rückruffunktion in Python einen dauerhaften Status benötigt. Wenn Sie Pythons Ertrag anstelle von Rückgabe verwenden, wird eine Funktion erneut eingegeben.
Dmitry Rubanovich
4

Eine Klasse definiert eine reale Entität. Wenn Sie an etwas arbeiten, das einzeln existiert und eine eigene Logik hat, die von anderen getrennt ist, sollten Sie eine Klasse dafür erstellen. Zum Beispiel eine Klasse, die die Datenbankkonnektivität kapselt.

Ist dies nicht der Fall, muss keine Klasse erstellt werden

Ashutosh
quelle
0

Das hängt von Ihrer Idee und Ihrem Design ab. Wenn Sie ein guter Designer sind, werden OOPs auf natürliche Weise in Form verschiedener Designmuster herauskommen. Für eine einfache Verarbeitung auf Skriptebene können OOPs Overhead sein. Betrachten Sie einfach die grundlegenden Vorteile von OOPs wie wiederverwendbar und erweiterbar und stellen Sie sicher, ob sie benötigt werden oder nicht. OOPs machen komplexe Dinge einfacher und einfacher. Halten Sie die Dinge einfach so oder so mit OOPs oder nicht mit OOPs. was immer einfacher ist, benutze das.

Mohit Thakur
quelle