Wie entwerfe ich eine Klasse in Python?


143

Ich hatte bei meinen vorherigen Fragen zum Erkennen von Pfoten und Zehen innerhalb einer Pfote eine wirklich großartige Hilfe , aber all diese Lösungen funktionieren jeweils nur für eine Messung.

Jetzt habe ich Daten , die aus bestehen:

  • ungefähr 30 Hunde;
  • jedes hat 24 Messungen (unterteilt in mehrere Untergruppen);
  • Jede Messung hat mindestens 4 Kontakte (einen für jede Pfote) und
    • Jeder Kontakt ist in 5 Teile unterteilt und
    • hat verschiedene Parameter wie Kontaktzeit, Ort, Gesamtkraft usw.

Alt-Text

Wenn man alles in ein großes Objekt steckt, wird es offensichtlich nicht gekürzt, also dachte ich, ich müsste Klassen anstelle der aktuellen Funktionen verwenden. Obwohl ich das Kapitel über Klassen von Learning Python gelesen habe, kann ich es nicht auf meinen eigenen Code anwenden ( GitHub-Link ).

Ich finde es auch ziemlich seltsam, alle Daten jedes Mal zu verarbeiten, wenn ich Informationen herausholen möchte. Sobald ich die Positionen jeder Pfote kenne, gibt es keinen Grund für mich, dies erneut zu berechnen. Außerdem möchte ich alle Pfoten desselben Hundes vergleichen, um festzustellen, welcher Kontakt zu welcher Pfote gehört (vorne / hinten, links / rechts). Dies würde zu einem Chaos werden, wenn ich weiterhin nur Funktionen verwende.

Jetzt suche ich nach Ratschlägen zum Erstellen von Klassen, mit denen ich meine Daten ( Link zu den komprimierten Daten eines Hundes ) auf vernünftige Weise verarbeiten kann.


4
Möglicherweise möchten Sie auch eine Datenbank verwenden (z. B. sqlite: docs.python.org/library/sqlite3.html ). Sie könnten ein Programm schreiben, das Ihre riesigen Datendateien liest und in Zeilen in Datenbanktabellen konvertiert. Als zweite Stufe können Sie dann Programme schreiben, die Daten aus der Datenbank ziehen, um weitere Analysen durchzuführen.
Unutbu

Du meinst so etwas wie ich hier gefragt habe @ubutbu? Ich habe vor, das zu tun, aber zuerst möchte ich in der Lage sein, alle Daten organisierter zu verarbeiten
Ivo Flipse

Antworten:


434

Wie man eine Klasse entwirft.

  1. Schreib die Wörter auf. Du hast damit angefangen. Einige Leute tun es nicht und fragen sich, warum sie Probleme haben.

  2. Erweitern Sie Ihre Wörter zu einfachen Aussagen darüber, was diese Objekte tun werden. Das heißt, schreiben Sie die verschiedenen Berechnungen auf, die Sie für diese Dinge durchführen werden. Ihre kurze Liste von 30 Hunden, 24 Messungen, 4 Kontakten und mehreren "Parametern" pro Kontakt ist interessant, aber nur ein Teil der Geschichte. Ihre "Positionen jeder Pfote" und "Vergleichen Sie alle Pfoten desselben Hundes, um festzustellen, welcher Kontakt zu welcher Pfote gehört" sind der nächste Schritt bei der Objektgestaltung.

  3. Unterstreichen Sie die Substantive. Ernsthaft. Einige Leute diskutieren den Wert davon, aber ich finde, dass es für erstmalige OO-Entwickler hilfreich ist. Unterstreichen Sie die Substantive.

  4. Überprüfen Sie die Substantive. Generische Substantive wie "Parameter" und "Messung" müssen durch spezifische, konkrete Substantive ersetzt werden, die für Ihr Problem in Ihrer Problemdomäne gelten. Einzelheiten helfen, das Problem zu klären. Generika lassen Details einfach außer Acht.

  5. Notieren Sie für jedes Substantiv ("Kontakt", "Pfote", "Hund" usw.) die Attribute dieses Substantivs und die Aktionen, an denen dieses Objekt beteiligt ist. Kürzen Sie das nicht ab. Jedes Attribut. "Datensatz enthält 30 Hunde" ist zum Beispiel wichtig.

  6. Identifizieren Sie für jedes Attribut, ob dies eine Beziehung zu einem definierten Substantiv oder zu einer anderen Art von "primitiven" oder "atomaren" Daten wie einem String oder einem Float oder etwas Irreduziblem ist.

  7. Für jede Aktion oder Operation müssen Sie identifizieren, welches Substantiv die Verantwortung trägt und welche Substantive lediglich teilnehmen. Es ist eine Frage der "Veränderlichkeit". Einige Objekte werden aktualisiert, andere nicht. Veränderbare Objekte müssen die volle Verantwortung für ihre Mutationen tragen.

  8. An diesem Punkt können Sie beginnen, Substantive in Klassendefinitionen umzuwandeln. Einige Sammelbegriffe sind Listen, Wörterbücher, Tupel, Mengen oder Named-Tupel, und Sie müssen nicht viel arbeiten. Andere Klassen sind komplexer, entweder aufgrund komplexer abgeleiteter Daten oder aufgrund einer Aktualisierung / Mutation, die durchgeführt wird.

Vergessen Sie nicht, jede Klasse einzeln mit unittest zu testen.

Es gibt auch kein Gesetz, das besagt, dass Klassen veränderlich sein müssen. In Ihrem Fall haben Sie beispielsweise fast keine veränderlichen Daten. Was Sie haben, sind abgeleitete Daten, die durch Transformationsfunktionen aus dem Quelldatensatz erstellt wurden.


24

Die folgenden Ratschläge (ähnlich den Ratschlägen von @ S.Lott) stammen aus dem Buch Beginning Python: Vom Anfänger zum Profi

  1. Schreiben Sie eine Beschreibung Ihres Problems auf (was soll das Problem tun?). Unterstreichen Sie alle Substantive, Verben und Adjektive.

  2. Gehen Sie die Substantive durch und suchen Sie nach möglichen Klassen.

  3. Gehen Sie die Verben durch und suchen Sie nach möglichen Methoden.

  4. Gehen Sie die Adjektive durch und suchen Sie nach möglichen Attributen

  5. Ordnen Sie Ihren Klassen Methoden und Attribute zu

Um die Klasse zu verfeinern, empfiehlt das Buch außerdem Folgendes:

  1. Schreiben Sie eine Reihe von Anwendungsfällen auf (oder lassen Sie sie sich ausdenken ) - Szenarien, wie Ihr Programm verwendet werden kann. Versuchen Sie, alle Funktionen abzudecken.

  2. Denken Sie Schritt für Schritt über jeden Anwendungsfall nach und stellen Sie sicher, dass alles abgedeckt ist, was wir benötigen.


Es wäre gut, einige Beispiele für die Art von Sätzen zu haben, die wir schreiben sollen.
Endolith

14

Ich mag den TDD-Ansatz ... Beginnen Sie also damit, Tests zu schreiben, wie das Verhalten aussehen soll. Und schreiben Sie Code, der übergeben wird. Machen Sie sich an dieser Stelle keine allzu großen Sorgen um das Design, sondern besorgen Sie sich eine Testsuite und eine Software, die erfolgreich ist. Machen Sie sich keine Sorgen, wenn Sie eine einzige große hässliche Klasse mit komplexen Methoden haben.

Während dieses ersten Prozesses finden Sie manchmal ein Verhalten, das schwer zu testen ist und nur aus Gründen der Testbarkeit zerlegt werden muss. Dies kann ein Hinweis darauf sein, dass eine separate Klasse gerechtfertigt ist.

Dann der lustige Teil ... Refactoring. Nachdem Sie eine funktionierende Software haben, können Sie die komplexen Teile sehen. Oft werden kleine Verhaltenstaschen sichtbar, die auf eine neue Klasse hinweisen. Wenn nicht, suchen Sie einfach nach Möglichkeiten, den Code zu vereinfachen. Serviceobjekte und Wertobjekte extrahieren. Vereinfachen Sie Ihre Methoden.

Wenn Sie git richtig verwenden (Sie verwenden git, nicht wahr?), Können Sie während des Refactorings sehr schnell mit einer bestimmten Zerlegung experimentieren und diese dann aufgeben und zurückkehren, wenn dies die Dinge nicht vereinfacht.

Wenn Sie zuerst getesteten Arbeitscode schreiben, sollten Sie einen intimen Einblick in die Problemdomäne erhalten, den Sie mit dem Design-First-Ansatz nicht leicht bekommen können. Das Schreiben von Tests und Code führt Sie an der Lähmung vorbei, bei der ich anfange.


1
Auch ich stimme dieser Antwort zu, obwohl es sehr nützlich sein kann, das Problem aufzuschlüsseln und mögliche Klassen zu identifizieren (dh "gerade genug" Software-Architektur zu verwenden), wenn das Problem von mehreren Teammitgliedern parallel bearbeitet werden soll.
Ben Smith

3

Die ganze Idee des OO-Designs besteht darin, Ihre Code-Map auf Ihr Problem abzustimmen. Wenn Sie beispielsweise den ersten Schritt eines Hundes möchten, tun Sie Folgendes:

dog.footstep(0)

Nun kann es sein, dass Sie für Ihren Fall Ihre Rohdatendatei einlesen und die Schritte berechnen müssen. All dies könnte in der Funktion footstep () versteckt sein, so dass es nur einmal vorkommt. Etwas wie:

 class Dog:
   def __init__(self):
     self._footsteps=None 
   def footstep(self,n):
     if not self._footsteps:
        self.readInFootsteps(...)
     return self._footsteps[n]

[Dies ist jetzt eine Art Caching-Muster. Das erste Mal, wenn es die Schrittdaten liest, werden sie anschließend nur von self._footsteps abgerufen.]

Aber ja, es kann schwierig sein, das OO-Design richtig zu machen. Überlegen Sie sich mehr, was Sie mit Ihren Daten tun möchten, und informieren Sie sich darüber, welche Methoden Sie für welche Klassen benötigen.


2

Das Aufschreiben Ihrer Substantive, Verben und Adjektive ist ein großartiger Ansatz, aber ich stelle mir das Klassendesign lieber vor, als die Frage zu stellen, welche Daten versteckt werden sollen .

Stellen Sie sich vor, Sie hätten ein QueryObjekt und ein DatabaseObjekt:

Das QueryObjekt hilft Ihnen beim Erstellen und Speichern einer Abfrage - Speichern ist hier der Schlüssel, da eine Funktion Ihnen beim Erstellen einer Abfrage helfen kann. Vielleicht könntest du bleiben : Query().select('Country').from_table('User').where('Country == "Brazil"'). Die Syntax spielt keine Rolle - das ist Ihre Aufgabe! - Der Schlüssel ist, dass das Objekt Ihnen hilft , etwas zu verbergen , in diesem Fall die Daten, die zum Speichern und Ausgeben einer Abfrage erforderlich sind. Die Stärke des Objekts beruht auf der Syntax, es zu verwenden (in diesem Fall eine clevere Verkettung) und nicht wissen zu müssen, was es speichert, damit es funktioniert. Wenn dies richtig gemacht wird, kann das QueryObjekt Abfragen für mehr als eine Datenbank ausgeben. Es würde intern ein bestimmtes Format speichern, könnte aber bei der Ausgabe leicht in andere Formate konvertiert werden (Postgres, MySQL, MongoDB).

Lassen Sie uns nun das DatabaseObjekt durchdenken . Was versteckt und speichert das? Natürlich kann nicht der gesamte Inhalt der Datenbank gespeichert werden, da wir deshalb eine Datenbank haben! Worum geht es also? Ziel ist es, die Funktionsweise der Datenbank vor Personen zu verbergen , die das DatabaseObjekt verwenden. Gute Klassen vereinfachen das Denken bei der Manipulation des internen Zustands. Für dieses DatabaseObjekt können Sie die Funktionsweise der Netzwerkaufrufe oder Stapelabfragen oder Aktualisierungen ausblenden oder eine Caching-Ebene bereitstellen.

Das Problem ist, dass dieses DatabaseObjekt riesig ist. Es stellt dar, wie auf eine Datenbank zugegriffen werden kann, sodass unter dem Deckmantel alles möglich ist. Es ist klar, dass Networking, Caching und Batching je nach System nur schwer zu bewältigen sind. Daher wäre es sehr hilfreich, sie zu verstecken. Wie viele Leute bemerken werden, ist eine Datenbank jedoch wahnsinnig komplex. Je weiter Sie von den unformatierten DB-Aufrufen entfernt sind, desto schwieriger ist es, die Leistung abzustimmen und zu verstehen, wie die Dinge funktionieren.

Dies ist der grundlegende Kompromiss von OOP. Wenn Sie die richtige Abstraktion auswählen, wird die Codierung einfacher (String, Array, Dictionary). Wenn Sie eine zu große Abstraktion auswählen (Database, EmailManager, NetworkingManager), wird sie möglicherweise zu komplex, um wirklich zu verstehen, wie sie funktioniert oder was zu tun ist erwarten von. Das Ziel ist es, die Komplexität zu verbergen , aber eine gewisse Komplexität ist notwendig. Eine gute Faustregel ist, zunächst ManagerObjekte zu vermeiden und stattdessen Klassen zu erstellen, die wie structsfolgt aussehen : Sie enthalten lediglich Daten und einige Hilfsmethoden zum Erstellen / Bearbeiten der Daten, um Ihnen das Leben zu erleichtern. Zum Beispiel beim EmailManagerStart mit einer aufgerufenen Funktion sendEmail, die ein EmailObjekt nimmt. Dies ist ein einfacher Ausgangspunkt und der Code ist sehr leicht zu verstehen.

Überlegen Sie sich in Ihrem Beispiel, welche Daten zusammen sein müssen, um zu berechnen, wonach Sie suchen. Wenn Sie beispielsweise wissen möchten , wie weit ein Tier läuft , können Sie AnimalStepund AnimalTrip(Sammlung von AnimalSteps) Klassen haben. Jetzt, da jede Reise alle Schrittdaten enthält, sollte es in der Lage sein, Dinge darüber herauszufinden, was vielleicht AnimalTrip.calculateDistance()Sinn macht.


2

Nachdem Sie Ihren verknüpften Code überflogen haben, scheint es mir, dass Sie an dieser Stelle besser dran sind, keine Hundeklasse zu entwerfen. Sie sollten stattdessen Pandas und Datenrahmen verwenden . Ein Datenrahmen ist eine Tabelle mit Spalten. Sie Datenrahmen würde Spalten haben , wie: dog_id, contact_part, contact_time, contact_location, usw. Pandas verwendet Numpy Arrays hinter den Kulissen, und es hat viele bequeme Methoden für Sie hat:

  • Wählen Sie einen Hund aus zB: my_measurements['dog_id']=='Charly'
  • Sichere die Daten: my_measurements.save('filename.pickle')
  • Erwägen Sie, pandas.read_csv()die Textdateien zu verwenden, anstatt sie manuell zu lesen.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.