OOP Anfängerfrage

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Hallo,

ich bin ein Änfänger in Sachen Python und OOP, habe bis vor 2 Jahren ein wenig mit Visual Basic programmiert- und vor 20 Jahren mit diversen Basic Dialekten (Atari, Ti-994A etc.).

Mit Python komme ich erstaunlich gut und schnell klar, an was es noch hapert ist OOP bzw. das Verständnis dafür. Diverse Beispiel im Net und Büchern mögen für sich zwar mehr oder weniger Sinn machen, aber ich fand noch nichts wofür ich direkte Verwendung hätte - und völlig sinnfrei nun eine Klasse „Auto“ oder ähnliches, ohne praktischen Bezug, zu erstellen bringt mich kaum wirklich weiter.
Für mich hat OOP noch was sehr zwanghaftes, bzw. erscheint es mir aufwendiger und umständlicher als wenn ich ähnliches per Funktionen ins Hauptprogramm einbaue. Verzichten könnte man darauf in Python zwar, lernen und verstehen will ichs trotzdem, zum einen aus Neugier zum anderen um eingebaute Klassen besser verstehen zu können und natürlich wegen der Wiederverwendbarkeit von Code.

Es geht mir in diesem Post also nur darum die Meinung von erfahrenen Leuten zu hören ob das was gleich kommt Sinn macht bzw. eine einigermaßen vernünftige Anwendung von OOP ist - wie gesagt, mir fehlt dazu noch das Gefühl.

Es geht um kleine Datenbanken, z.B. DVD Liste, Jogginglauftagebuch und ähnliches. Programmiert habe ich auf Python nun schon 2 solche Programme und die funktionieren auch tadellos, aber eben ohne selbstgebaute Klassen und Objekte.
Auffällig dabei ist das ein wesentlicher Teil der beiden Programmen sich um eine Basis, nämlich eindimensionale Listen und deren Manipulation dreht, nur halt mit unterschiedlichen Parametern.
Und genau da sehe ich nun erstmalig einen Sinn einer Klasse, vor allem wegen dem wiederverwendbaren Code.
Die einzelnen Datensätze wären also in meinem Denkansatz keine Objekte, sondern die ganze Liste würde ausschließlich von der Klasse bearbeitet werden, also statische Methoden und eine Klasse ohne Objekte.
Kurz noch zum Listenaufbau, den ich bisher verwendete:
Die Liste enthält eine Tabelle (Zeilen, Spalten = Datensätze), wobei sozusagen Zeile an Zeile geschrieben wird >> inhalt[0]= 1. Zeile, 1. Spalte; inhalt[1] = 1. Zeile, 2. Spalte ..... ...... inhalt[8] = 2. Zeile, 1. Spalte .... ..... inhalt[16] = 3. Zeile, 1. Spalte u.s.w.

Und das wäre die angedachte (statische) Klasse:

Klassenname: MeineListe
Attribute:
name (unter der die Tabelle mit pickle gespeichert und geladen wird)
idKey (eindeutige Referenz der Zeilen/Datensätze)
wert (zum Übergeben und holen von Einträgen als Tupel)

zeile (zum direkten ansprechen von Tabellenplätzen - Fehlersuche und Testen)
spalte( -wie zeile- )

Statische Methoden:
creatTabelle (neue Tabelle generieren)
plusSpalte (Tabelle nachträglich um eine Spalte erweitern)

setWert (neuen Wert in die Liste schreiben - Übergabe per Tupel)
getWert (Werte (Datensatz) aus Liste holen - per Tupel - brauche ich mehr zum testen siehe getNext)

setZeile (Liste um eine Zeile erweitern (neuen Datensatz anfügen))
destZeile (Datensatz entfernen)
searDaten (Suchen nach Schlüsselwörtern und markieren der betroffenen Datensätze)
getNext ( nächsten (evtl.) per Suche markierten Datensatz zurückgeben)
reset (per Suche markierte Datensätze rücksetzen)

loadTabelle (Tabelle laden)
saveTabelle (Tabelle speichern)


Der erste Reflex mag nun vielleicht sein „Warum verwendest du keine richtig Datenbank im Hintergrund ?“.
- Ich arbeite sehr gerne mit Listen, zudem habe ich mit Datenbanken keine Erfahrung, die Fehlersuche fällt mir also mit Listen wesentlich leichter da ich hier (gefühlt) alles unter Kontrolle habe und alles (programmtechnisch) auf einer Ebene liegt.
- Ich als Pythonanfänger mich nun nicht zeitgleich auch noch SQL Syntax herumschlagen will.

Nochmal, es geht mir nur um die Sinnhaftigkeit des Angedachten und ob das eine „vernünftige Art“ der Anwendung von OOP ist :K
Der Code an sich ist sicher kein Problem, das habe ich nun zweimal gemacht, nur dass da halt die ganzen Listenmanipulationen in bester Basic Tradition :D (auch über Funktionen) über das ganze Hauptprogramm verteilt sind.

MfG
Thomas
BlackJack

@tom1968: Ich denke Du hast schon in der Version mit den Funktionen etwas falsch gemacht. Warum um Himmels willen speicherst Du eine eigentlich zweidimensionale Datenstruktur in einer eindimensionalen Liste‽ Dann muss man doch ohne Ende mit Indexzahlen hantieren und Rechnen, was man in Python eigentlich nicht macht, weil man dass in aller Regel nicht so kompliziert machen muss. Eine Tabellenzeile lässt sich als Liste darstellen und die Tabelle dann als Liste von diesen Zeilenlisten. Um dann zum Beispiel eine komplette Tabelle auszugeben, könnte man so etwas schreiben:

Code: Alles auswählen

def print_table(table):
    for row in table:
        print ', '.join(row)
Jetzt vergleiche das mal mit dem Aufwand den Du bei Deiner flachen Liste für die gleiche Ausgabe treiben müsstest.

Ich weiss jetzt nicht ob Du den Begriff „statisch” tatsächlich richtig verwendest, aber Du willst ganz bestimmt keine statische Klasse und schon gar keine Klasse in der alle Methoden statisch sind. Denn dann sind es keine Methoden mehr, sondern einfach nur Funktionen. Und für Funktionen braucht man keine Klasse, die schreibt man in der Regel auf Modulebene.

Ein Klassenname wie `MeineListe` ist zu nichtssagend. Hier würde `Tabelle` passen, denn das ist ja was Du modellieren möchtest.

Die Namen in Deinem Entwurf halten sich nicht an den Style Guide for Python Code.

`idKey` habe ich nicht verstanden. `wert`, `zeile`, und `spalte` machen keinen Sinn. Für die Übergabe verwendet man Argumente bei dem Methoden und keine Attribute auf dem Objekt.

Der Denglish-Mix bei den Namen ist unschön, da solltest Du Dich auf eine Sprache — vorzugsweise Englisch — festlegen. Ebenfalls vermeided solltest Du Abkürzungen die nicht allgemein gebräuchlich sind. Bei `searDaten()` muss man rätseln was gemeint ist, während bei `destZeile()` natürlich klar ist, dass hier „destination Zeile” gemeint ist — oder halt auch nicht. ;-)

`creatTabelle` macht keinen Sinn, denn dafür ist ja die `__init__()`-Methode vorgesehen.

Bei dem Namen `setZeile()` erwartet man das eine bestehende Zeile gesetzt/ersetzt wird, aber nicht das eine neue Zeile angehängt wird. Das würde ich eher `add_zeile()` beziehungsweise `add_row()` nennen.

Für den Zugriff auf Zeilen und das löschen bieten sich die `__setitem__()`, `__getitem__()`, und `__delitem__()`-Methoden an.

Beim Suchen und Iterieren stellt sich die Frage ob das mit dem Markieren tatsächlich sein muss, oder ob man nicht einfach bei einer Suche ein neues Tabellenobjekt mit dem Suchergebnis bekommt. Dann kann man sich die Verwaltung von Markierungen sparen und ein allgemeines Iterieren über Tabellenzeilen mittels `__iter__()`-Methode implementieren.

Das laden einer Tabelle ist dann mal ein sinnvoller Einsatz einer statischen Methode oder noch besser einer Klassenmethode (siehe `classmethod()`).

Über einen eigenen Typ `Row` könnte man sich Gedanken machen, wenn man doch Markierungen verwenden will, denn irgendwo muss man ja auch speichern ob ein Datensatz markiert ist oder nicht. Dann könnte man zusätzlich zum Indexzugriff innerhalb einer Zeile auch noch Spaltennamen einführen und einen Zugriff darüber einführen und einen Rückverweis auf die Tabelle zu welcher der Datensatz gehört und vielleicht auch noch Metadaten wie den Typ der jeweiligen Spalte.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

@Black Jack: Vielen Dank für Deine ausführliche Antwort
Warum um Himmels willen speicherst Du eine eigentlich zweidimensionale Datenstruktur in einer eindimensionalen Liste‽ ...
Freilich könnte man die Liste auch mehrdimensional machen, hatte ich ursprünglich auch vor - das klappte dann aber nicht gleich und so machte ich die Liste erstmal eindimensional (flach) - inzwischen komme ich damit aber so gut klar dass ich am überlegen bin ob ich überhaupt auf eine zweidimensionale Liste umsteigen soll. Groß Berechnen muss man da ja auch nichts, ich verwnede ein globale Variable (Zeiger) die auf den aktuell auszugebenden oder zu verarbeitenden Datensatz (Zeile) zeigt...und der Wert dieses Zeigers verändert sich bei mir dann halt nicht um 1 wenn ich zum nächsten oder vorherigen Datensatz springe, sondern um die Spaltenbreite (die mir ja bekannt ist). ( "zeiger += 8" z.B.) Also keine große Sache :wink:
Ich weiss jetzt nicht ob Du den Begriff „statisch” tatsächlich richtig verwendest, aber Du willst ganz bestimmt keine statische Klasse und schon gar keine Klasse in der alle Methoden statisch sind. Denn dann sind es keine Methoden mehr, sondern einfach nur Funktionen. Und für Funktionen braucht man keine Klasse, die schreibt man in der Regel auf Modulebene.
Ja, doch genau das ist es was mir vorschwebte (allerdings dachte ich nicht dass das ohne eine Klasse funktioniert - habs eben ausprobiert, funktioniert - also keine Klasse, nur Funktionen auf Modulebene)
Über einen eigenen Typ `Row` könnte man sich Gedanken machen, wenn man doch Markierungen verwenden will, denn irgendwo muss man ja auch speichern ob ein Datensatz markiert ist oder nicht.
Das bewerkstelligte ich bisher über eine zusätzliche Spalte, die nicht sichtbar ist - dort steht dann beispielsweise bei jedem auszugebenden Datensatz ein "x". Wird nach einem Schlagwort gesucht wird das x überall dort rausgenommen wo das Schlagwort nicht auftaucht. Bei der Asugabe springt der "Zeiger" dann von Datensatz zu Datensatz, steht dort in der Spalte kein "x" gehts, ohne Ausgabe, zum nächsten Datensatz.
Die Namen in Deinem Entwurf halten sich nicht an den Style Guide for Python Code.
Ja, vollkommen richtig :oops:
Bisher spielte das für mich keine große Rolle, da ich hier nicht im Team arbeite und bisher auch nie Code online stellte. Aber wenn man Fragen hat und andere was mit dem eigenen Code anfangen können sollen, sollte der sich den üblichen Gepfogenheiten anpassen - ich arbeite daran :wink:
(Muss zu meiner Verteidigung aber auch anfügen dass ich erst seit drei Wochen mit Python arbeite, das mit den Konventionen wollte ich mir für später aufheben - oder pragmatisch "Erstmal muss es überhaupt funktionieren" :D )

.....
Wenn ich dich richtig verstehe, bist Du davon ausgegenagen dass ich eine Klasse erstellen will die genau ein Objekt - und dieses Objekt (Instanz) stellt dann meine Tabelle dar ? Das wäre natürlich auch eine Idee, momentan sehe ich darin aber noch keinen Vorteil gegenüber der "Funktionen auf Modulebene" Variante.
Die bei OOP oft angesprochene Datenkapselung und Wiederverwendbarkeit sind mit der "Funktionen auf Modulebene" Variante in meine Augen voll erfüllt.
Aber da bin ich für Gegenargumente sehr empfänglich, vielleicht verstehe ich dann den Sinn von OOP am praktischen, mich betreffenden Beispiel.
BlackJack

@tom1968: Nun es ist natürlich nicht gross etwas berechnen, aber Du musst halt überhaupt etwas berechnen, was nicht nötig ist. Und Du hast einen „Zeiger” der eigentlich eine Zahl ist, die als Index indirekt zum Zugriff benutzt wird. Dann brauchst Du zusätzlich noch eine Variable wo vermerkt wird wieviele Elemente zu einem Datensatz gehören. Und man sieht der Datenstruktur ihre eigentliche Struktur nicht an. Das macht das ganze undurchsichtig und komplizierter zu verstehen als es sein müsste. Ich verweise noch mal auf die `print_table()`-Funktion aus meinem letzten Beitrag. Da braucht man keine einzige Variable für eine Indirektion über einen Index und auch keine zusätzliche Information wieviele Elemente in einem Datensatz enthalten sind.

Mit Deinem 'x' zum markieren hast Du etwas in den Daten das gar nicht wirklich zu den Nutzdaten der Tabelle gehört, sondern Metainformation zur Verwaltung der Tabelle sind. Ich würde das wie gesagt komplett weglassen und eine Filterfunktion zur Verfügung stellen.

Zumindest bei Deinem Ansatz mit der eindimensionalen Liste haben die Funktionen den Nachteil dass Du *zwei* Informationen hast, welche eine Tabelle beschreiben: Die Liste und die Anzahl der Elemente eines Datensatzes. Die müssen an jede Funktion übergeben werden, die etwas mit den Datensätzen macht und zwar als getrennte Informationen. Der Programmierer ist dafür verantwortlich immer die beiden zusammengehörigen Informationen zu übergeben.

Bei einer 2D-Liste enthält die erst einmal alles was man braucht. Aber wenn man das Tabellenkonzept erweitern möchte, bekommt man wieder genau das gleiche Problem. Zum Beispiel wenn man einen Namen für die Tabelle oder für Tabellenspalten haben möchte.
Benutzeravatar
snafu
User
Beiträge: 6894
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@tom1968: Ganz allgemein gesagt liegt ein großer Vorteil von Klassen darin, dass man sich gegenwärtige Zustände merken kann. Wenn du die Klasse also mit ein paar passenden Attributen initialisierst (mittels `.__init__()`), dann kannst du dir z.B. immer merken, an welcher Position in deiner Liste du gerade bist. Das Einfügen von neuen Einträgen geht - wie du wahrscheinlich schon richtig vermutet hast - über Methoden á la `.fuegeEintragHinzu()`. Dort würde dann meinetwegen einfach ein `self.eintraege.append(eintrag)` ausführt werden. Man sollte halt nur beachten, dass man mit solchen Forwarding-Methoden möglichst nicht das komplette `list()`-Interface nachbaut, sondern nur eine limitierte Variante liefert - ansonsten wirkt die Klasse total überfrachtet. `.fuegeHinzu()`, `.entferne()`, `.gibEintraege()` wären hier wohl die essentiellen Sachen.

Mehr an allgemeinen Tipps fällt mir gerade nicht ein. Aber wenn du Detailfragen hast, dann frag. :)
Benutzeravatar
kbr
User
Beiträge: 1509
Registriert: Mittwoch 15. Oktober 2008, 09:27

tom1968 hat geschrieben: ich verwnede ein globale Variable (Zeiger) die auf den aktuell auszugebenden oder zu verarbeitenden Datensatz (Zeile) zeigt...und der Wert dieses Zeigers verändert sich bei mir dann halt nicht um 1 wenn ich zum nächsten oder vorherigen Datensatz springe, sondern um die Spaltenbreite (die mir ja bekannt ist). ( "zeiger += 8" z.B.)
@tom1968: Auch wenn Du ursprünglich bezüglich Objekte gefragt hattest, lass Dich hier ruhig einmal auf BlackJacks Kritik der Datenstruktur ein. Python ist nicht einfach ein anderes C, sondern der Mehrwert steckt in der "pythonischen" Programmierung. Das ist kein genau definierter Begriff, sondern beschreibt eher die Nutzung der Sprache gemäß ihrer Philosophie. Wenn Du dies befolgst wirst Du nach einiger Zeit erkennen, warum viele hier Python bevorzugt einsetzen - und möglicherweise wirst Du dann auch dazu zählen.
BlackJack

@tom1968: Dazu noch mal eine (ungetestete) Umsetzung Deiner Klassen-API als Funktionen mit einer 2D-Liste:

Code: Alles auswählen

import csv


def create_table():
    return list()


def add_row(table, row):
    table.append(row)


def add_column(table, value=''):
    for row in table:
        row.append(value)


def get_row(table, i):
    return table[i]


def delete_row(table, i):
    del table[i]


def get_at(table, (x, y)):
    return get_row(table, y)[x]


def set_at(table, (x, y), value):
    get_row(table, y)[x] = value


def search(table, keyword):
    return (row for row in table if any(keyword in value for value in row))


def load_csv(filename, delimiter=';'):
    with open(filename, 'rb') as csv_file:
        return list(csv.reader(csv_file, delimiter))


def save_csv(table, filename, delimiter=';'):
    with open(filename, 'wb') as csv_file:
        csv.writer(csv_file, delimiter).writerows(table)
Das dürfte mit einer flachen Liste deutlich komplizierter aussehen und nicht mit 1—2 Zeilen pro Funktion zu machen sein.
Boa
User
Beiträge: 190
Registriert: Sonntag 25. Januar 2009, 12:34

Hallo Tom,

Ich gehe kurz auf das Prinzip der OOP ein und versuche dann auf dein Problem bezogen einige praktische Beweggründe hervorzuheben.
OOP verwendet ein Abstraktions Konzept. Dieses kommt aus dem alltäglichen Leben: Wir nehmen Objekte wahr und unterteilen sie in Klassen. Der Begriff Klasse ist dabei ähnlich wie in der Biologie zu sehen, wo man einzelne Tiere nach Arten klassifiziert. Louis ist meine Hauskatze (der Fachbegriff dieser Klasse ist Felis catus). Es gibt viele Objekte einer Klasse. Klassen wie Felis catus existieren nicht, sondern stellen eine Abstraktion (Verallgemeinerung) dar, die uns hilft ein Objekt zu beschreiben. Wenn du weißt, dass ich eine Hauskatze habe, weißt du dass sie ein Säugetier ist, schnurrt, miaut, kratzt, Mäuse fängt und vieles mehr, ohne meine Katze je gesehen zu haben.
In der OOP nutzt man das aus. Anstatt lediglich die Funktionen zu schreiben, fässt man mehrere Funktionen und dazugehörige Daten in ein Objekt zusammen: Also hat man statt eines Pragramms mit Funktionen tabelle_erstellen(...), umdrehen(liste,...) zeile_hinzufügen(liste,...) ein tatsächliches Objekt "Tabelle" zu dem die Funktionen gehören: tabelle = Tabelle(); tabelle.umdrehen(); tabelle.füge_zeile_hinzu()
Dadurch kann man das Programm einfacher verstehen. Wenn du weißt, dass es in dem Programm ein Tabellen Objekt gibt, weißt du dass man dem Objekt Zeilen hinzufügen, Spalten löschen und Daten hineinschreiben kann, wie man das aus Tabellen aus der realen Welt kennt und man weiß auch gleich was Methoden machen, die zeile_hinzufügen o.ä. heißen. Wenn man hingegen ein Programm sieht, mit Funktionen wie destZeile(), searDaten(), getNext() könnte das auf eine x beliebige Datenstruktur hinweisen, insbesondere dann, wenn man den Funktionen eine Liste als Datenstruktur übergeben soll.
Einer Methode muss man die Datenstruktur nicht mitgeben, damit sie wissen worauf sie arbeiten sollen. Insbesondere hast du so die Möglichkeit eine flache Datenstruktur zu nutzen, ohne jemanden der das Objekt Tabelle nutzt zu verwirren. Solltest du dich dazu entscheiden doch eine 2D Datenstruktur zu verwenden, musst du die Parameter der Methoden nicht mal verändern! Du kannst weiterhin tabelle.umdrehen(); tabelle.füge_zeile_hinzu() aufrufen. Bei den Funktionen müsstest du stattdessen jeden Parameter "liste" mit "2DStruktur" tauschen. Wenn du die Funktionen in anderen Programmen verwendest müsstest du diese umschreiben. Daher ist die "Wiederverwendbarkeit" der OOP Lösung besser. Das Prinzip dahinter ist die sogenannte "Kapselung" oder das Geheimnisprinzip. Das bedeutet, du versteckst die eigentliche Umsetzung hinter den Methoden, sodass du diese änder kannst, ohne dass sich die Methoden ändern.
Man schreibt Code wie: ich habe eine Tabelle programmiert, die die üblichen Eigenschaften und Funktionen hat, anstelle von: ich habe eine Funktion zeile_hinzufügen, erstellen, hole nächstes Element..., welche auf einer Liste operieren als wäre sie eine Tabelle.

Natürlich kann man auch gleich bessere Bezeichner für die Funktionen nehmen, die nahelegen, dass man es mit Tabellen zu tun hat, ohne OOP Syntax zur Hilfe zu nehmen. Aber OOP ist eben nicht nur die Syntax, die die Sprache bereitstellt. Und sobald man anfängt die Tabelle als Objekt in den Vordergrund zu stellen (erstelle_tabelle(), füge_zeile_zu_tabelle_hinzu(), liefere_nächstes_element_der_tabelle()) programmiert man bereits objekt orientiert. Es kommt eher auf die Denkweise an, aber der Syntactic Sugar der OOP Programmiersprachen bietet einen allgemein verständlichen, also lesbaren und wiederverwendbaren Standard: tabelle = Tabelle(); tabelle.füge_zeile_hinzu(); tabelle.nächstes_element()

Ich meine die einfachste und schönste Art OOP zu lernen besteht darin bestehende Klassen zu verwenden. Dabei stellst du optimaler Weise fest, wie elegant eine gut programmierte Klasse sich verwenden lässt und wie einfach sie zu verstehen ist, auch wenn es aufwändiger ist diese zu schreiben, als ein paar Funktionen. So dass du dich irgendwann fragst, wie du selbst eine soche Klasse erstellen kannst, damit du die Lösung genauso einfach verwenden kannst.

EDIT: In Python hast du dafür die optimalen Voraussetzungen. Die cvs Klassen in BlackJack's Beispiel sind Reader und Writer. Das sind allerdings nicht die besten Beispiele, da sie nicht über die standard Syntax, also Reader() und Writer() erzeugt werden. Herkömmlicherweise würde das so aussehen:

Code: Alles auswählen

import cvs
def save_csv(table, filename, delimiter=';'):
    with open(filename, 'wb') as csv_file:
        cvs_writer = Writer(csv_file, delimiter) # cvs Objekt erzeugen
        cvs_writer.writerows(table) # methode writerows aufrufen
BlackJack

@Boa: Anmerkungen:

Klassen (wie `Katze`) ”existieren” in Python im gleichen Sinne wie Exemplare von `Katze` (wie Dein `louis`) existieren — es sind alles Objekte. Damit unterscheidet sich Python zum Beispiel von Sprachen wo Klassen im Quelltext statisch existieren aber zur Laufzeit nicht mehr — dort gibt es dann nur noch Exemplare davon.

Bei dem gegebenen Beispiel mit der Tabelle als 2D-Liste sieht man die Vorteile die Du ansprichst übrigens nicht so stark. Eine Lösung mit Funktionen…

Code: Alles auswählen

import table


def main():
    member_filename = 'members.csv'
    club_members = table.load_csv(member_filename)
    for member in table.search(club_members, 'Peter'):
        print member
    table.add_column(club_members)
    table.add_row(club_members, ['Max', 'Mustermann', ''])
    table.add_row(club_members, ['Dr.', 'No', 'VIP'])
    table.save_csv(club_members, member_filename)
…unterscheidet sich nicht so grundlegend von einer API mit einer Klasse:

Code: Alles auswählen

from table import Table


def main():
    member_filename = 'members.csv'
    club_members = Table.load_csv(member_filename)
    for member in club_members.search('Peter'):
        print member
    club_members.add_column()
    club_members.add_row(['Max', 'Mustermann', ''])
    club_members.add_row(['Dr.', 'No', 'VIP'])
    club_members.save_csv(member_filename)
Die Funktionen können nicht auf x-beliebige Datenstrukturen hinweisen, denn sie stecken in einem Modul und wenn das ordentlich benannt ist, dann ist auch klar das alle auf einer Tabelle operieren. Solange man nicht selber direkt am Tabellenobjekt herum manipuliert, sondern immer nur die Funktionen dafür verwendet, kann man auch die Repräsentation der Daten zwischen 1D- und 2D-Liste ändern, ohne dass man mehr als die Funktionen im `table`-Modul ändern müsste. Also kein Unterschied zu einer Klasse an dieser Stelle.

Ich denke OOP kommt erst bei Polymorphie und Vererbung so richtig zum tragen (und bei Python auch wenn man einen Verbunddatentyp haben möchte). Das Andere kann man auch mit Funktionen noch ordentlich lösen. Auch Polymorphie und Vererbung kann man mit Funktionen noch auf die Reihe kriegen, aber dann muss man teilweise unschönen Code schreiben für den Python syntaktischen Zucker bietet, der die Sache wesentluch einfacher und lesbarer macht.

Die Skepsis von tom1968 kann ich hier verstehen, weil bei dem vorliegenden Praxis-Beispiel noch kein wirklicher Vorteil von OOP zum tragen kommt, und er kein Künstliches haben wollte, wo man das jetzt nur des Beispiels wegen reindrückt.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Vielen Dank für eure Beiträge.

Gut, zweidimensionale Tabellen sind in meinem Fall wohl doch eindeutig besser und ich werde das nutzen. Neben dem schlankeren, eleganteren Code scheint mir das auch leichter wartbar. (z.B. wenn Spalten hinzugefügt werden).
Vor allem das Codebeispiel von BlackJack überzeugt da doch, Danke dafür.

Klassen scheinen in diesem Fall keine unbedingte Verbesserung, gegenüber Funktionen in Modulen zu sein. Ich will das aber trotzdem nun mit Klassen versuchen - Also gut möglich, dass da dann noch Fragen kommen ;)

@kbr
...Python ist nicht einfach ein anderes C, sondern...
Auch ein guter Punkt, „C“ ist es bei mir zwar nicht, aber meinen bisheriger Pythoncode könnte man mit geringen (Syntax)Änderungen auch in Visual Basic laufen lassen :)

Eine kleine Frasge habe ich abeer noch, auch wenn es nichts mehr mit OOP zu tun hat: In euren Listnings speichert ihr die Tabelle als *.csv Dateien ab - Speicht bei so einer Tabelle etwas gegen das Abspeichern der Tabellenfeldvariable per pickle, oder wo wären die Vorteile von *.csv ? (Der einzige Vorteil den ich mir selbst zusammenreimen kann wäre dass man die Daten per Editor (außerhalb des Programms) verändern könnte und dass die Datei auch von anderen Programmierumgebungen (z.B. Java) gelesen werden könnte)

Thomas
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

tom1968 hat geschrieben: (Der einzige Vorteil den ich mir selbst zusammenreimen kann wäre dass man die Daten per Editor (außerhalb des Programms) verändern könnte und dass die Datei auch von anderen Programmierumgebungen (z.B. Java) gelesen werden könnte)
Na, das kann doch durchaus ein gewichtiger Vorteil sein ;-)

Man kann auch ein beliebiges anderes Format nehmen; JSON, XML, HTML, Textdatei mit fixen Breiten, ... bei einer Tabellen-Struktur bietet sich CSV irgend wie natürlich an :-)

Ich würde die Fragerichtung also eher umkehren: Was spricht gegen CSV?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Was spricht gegen CSV
Nichts :D
"pickle" wurde halt in 2 Büchern als so tolle Sache beschrieben und funktionieren tut es bei mir auch, daher ....
Aber das kann ich später immer noch entscheiden und ggf. umändern, das Speichern und Laden funktioniert in Python ja recht unkompliziert.

So ich habe nun begonnen die Klasse zu erstellen, der erste Schritt war nun dass das Ganze überhaupt funktioniert, die Daten geladen werden, ein Objekt erzeugt werden kann und damit gearbeitet werden kann.
Und das funktioniert auch soweit:
Dennoch wollte ich diese paar Zeilen hier nochmal (zur Prüfung) zeigen, denn eine Frage habe ich dazu noch:
Ist es richtig dass ich die Datei in der __init__ Methode von der Festplatte lade und der Tabellenvaraible zuordne ? - Der erste Versuch, das Laden im Hauptprogramm zu erledigen und die Tabelle (also die nach dem Laden existierende Variable tabelle[][]) per Argument beim Erzeugen des Objekts zu übergeben, funktionierte nicht.

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf - 8 *-

import pickle

class Tabelle():
	
	def __init__ (self, dateiname):
		inhalt = open (dateiname, "rb")
		tabelle = pickle.load(inhalt)
		inhalt.close
		self.tabelle = tabelle
		print (tabelle[0][0])				# nur zum Test
		
	def ausgabe(self,z,s):					# nur zum Test
		print (self.tabelle[z][s])			# nur zum Test


		
dateiname ="laufen2dim.dmp"	
tabelle = Tabelle(dateiname)
z = 1									# nur zum Test
s = 1									# nur zum Test
tabelle.ausgabe(z,s)      					# nur zum Test
BlackJack

@tom1968: Du schliesst die Datei nicht — einfach nur `close` reicht nicht, Du musst die Methode auch *aufrufen*. Oder noch besser die ``with``-Anweisung verwenden.

Laden im Hauptprogramm und übergeben als Argument sollte funktionieren. Gegen das Laden in der `__init__()` spricht das man ein Tabellenobjekt vielleicht auch erstellen möchte wenn es keine Datei dazu gibt. Zum Beispiel bevor es überhaupt eine Datei gibt und man mit einer leeren Tabelle anfangen möchte, oder zum Beispiel wenn man temporäre Tabellen haben möchte die nur im Speicher existieren.

Ist vielleicht noch etwas zu fortgeschritten, aber zum Laden würde man eine statische (`staticmethod()`) oder noch besser eine Klassenmethode (`classmethod()`) implementieren, statt die Daten im Hauptprogramm zu laden.
Benutzeravatar
snafu
User
Beiträge: 6894
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

tom1968 hat geschrieben:Der erste Versuch, das Laden im Hauptprogramm zu erledigen und die Tabelle (also die nach dem Laden existierende Variable tabelle[][]) per Argument beim Erzeugen des Objekts zu übergeben, funktionierte nicht.
Dann zeig mal den Code zur Übergabe des Arguments und die aufgetretene Fehlermeldung, falls es eine gab. Denn man kann problemlos auch mehrdimensionale Listen als Argumente herumreichen. "Funktioniert nicht" ist nie eine besonders gute Fehlerbeschreibung. ;)
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

ok, dann so ?

Code: Alles auswählen

class Tabelle():
	
	def __init__ (self, tabelle):
		self.tabelle = tabelle
		print (tabelle[0][0])				# nur zum Test
		
	def ausgabe(self,z,s):					# nur zum Test
		print (self.tabelle[z][s])			# nur zum Test


		
dateiname ="laufen2dim.dmp"	
inhalt = open (dateiname, "rb")
tabelle = pickle.load(inhalt)
inhalt.close
Wegen dem Schließen mit "with" muss ich mich noch schlau machen.
Boa
User
Beiträge: 190
Registriert: Sonntag 25. Januar 2009, 12:34

@tom:
Hinter close fehlen die Klammern.

@BlackJack:
Dass Klassen in Python Objekte sind weiß ich zwar, ich denke aber das kann man beim lernen von OOP im Allgemeinen erst mal ignorieren um es einfacher zu halten.
Eine Lösung mit Funktionen…
…unterscheidet sich nicht so grundlegend von einer API mit einer Klasse.
Man kann eben auch ohne den Syntactic Sugar wie Klassen Objekt orientiert programmieren. Dein Beispiel in dem du ein Modul zur kapselung verwendest belegt das ziemlich gut. Auch für Polimorphie und Vererbung braucht man keine Klassen. Aber der Code ist lesbarer wenn man einen von der Sprache verwendeten Mechanismus verwendet.
Das meinte ich mit:
Natürlich kann man auch gleich bessere Bezeichner für die Funktionen nehmen, die nahelegen, dass man es mit Tabellen zu tun hat, ohne OOP Syntax zur Hilfe zu nehmen.
Die Verwendung von Modulen ist ein besserer Bezeichner.
Ich meine, dass OOP eher eine Frage ist wie man den Code versteht und schreibt, als die Verwendung von bestimmten Sprachelementen. Man kann andersherum auch Klassen verwenden ohne Objekt orientiert zu programmieren.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Dann zeig mal den Code zur Übergabe des Arguments und die aufgetretene Fehlermeldung, falls es eine gab. Denn man kann problemlos auch mehrdimensionale Listen als Argumente herumreichen. "Funktioniert nicht" ist nie eine besonders gute Fehlerbeschreibung. ;)
Das hast Du natürlich recht, komischerweise funktioniert es jetzt, ich kann den Fehler auch nicht mehr reproduzieren.

@Boa Danke !!
Benutzeravatar
snafu
User
Beiträge: 6894
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

tom1968 hat geschrieben:ok, dann so ?

Code: Alles auswählen

class Tabelle():
	
	def __init__ (self, tabelle):
		self.tabelle = tabelle
		print (tabelle[0][0])				# nur zum Test
		
	def ausgabe(self,z,s):					# nur zum Test
		print (self.tabelle[z][s])			# nur zum Test


		
dateiname ="laufen2dim.dmp"	
inhalt = open (dateiname, "rb")
tabelle = pickle.load(inhalt)
inhalt.close
Ja, genau so wäre die übliche Übergabe von `tabelle`. An was hattest du denn gedacht? Mehrdimensionale Listen / Arrays übergibt man doch eigentlich immer auf diese Art... :o
tom1968 hat geschrieben:Wegen dem Schließen mit "with" muss ich mich noch schlau machen.
Um bei den von dir gewählten Bezeichnern zu bleiben:

Code: Alles auswählen

with open(dateiname, "rb") as inhalt:
    tabelle = pickle.load(inhalt)
Ein `.close()` entfällt damit. Und das ist auch der übliche Weg, wie man in Python beim Öffnen von Datenströmen grundsätzlich vorgehen würde. Man würde mit `tabelle` im Übrigen dann wieder außerhalb des `with`-Blocks weiterarbeiten. Sobald der Code diesen Block verlässt (also wenn `pickle.load()` abgearbeitet wurde), kommt es zum automatischen Schließen des Datenstroms bzw der zuvor geöffneten Datei. `tabelle` wird dabei weiterhin als Name erreichbar sein, da die Regeln der Sichtbarkeit in Python etwas weniger streng gehandhabt werden, als man das von manchen anderen Sprachen her kennt.
BlackJack

@Boa: Ich würde das mit den Funktionen nicht objektorientiert nennen, denn spätestens bei Polymorphie und Vererbung wird das mit den Funktionen schnell sehr umständlich.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

tom1968 hat geschrieben:Klassen scheinen in diesem Fall keine unbedingte Verbesserung, gegenüber Funktionen in Modulen zu sein. Ich will das aber trotzdem nun mit Klassen versuchen - Also gut möglich, dass da dann noch Fragen kommen ;)
Ja das stimmt — nicht jeder Anwendungsfall ist sinnvoll für Klassen einsetzbar. Ich setze es meist ein wenn ich Daten und Möglichkeiten diese Daten zu verarbeiten koppeln will. Dafür ist es gut, weil das eben eine Einheit bildet und so besser wartbar ist.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Antworten