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

Die erste Methode meiner Tabellen-Klasse ist nun geschrieben und funktioniert auch soweit.
Ich könnte jetzt so weitermachen, der Rest der Methoden wäre schnell geschrieben. Ich wollte es aber hier dennoch nochmal zeigen, es geht mir vor allem um die Verwendung der "zeiger" Variable in der Klasse - ob man das so machen kann (klar kann man, funktioniert ja :) )Es geht mir aber darum ob das einigermaßen "Klassenlike" ist, oder ob ich damit gegen grundsätzliche Konventionen in Klassen verstoße (die mir dann später Probleme bereiten könnten).

Der Sinn der "gebe_naechsten_Datensatz" -Methode ist den nächsten Datensatz zurückzugeben, damit dieser bearbeitet oder ausgegeben werden kann. Zurückgegeben werden sollen aber nur Datensätze bei denen in Spalte 13 ein "x" steht - später soll bei Datensätzen die Beisielsweise ein bestimmtes (Such)Wort nicht enthalten das "x" duch "n" ersetzt werden, damit diese Datensätze nicht zurückgegeben werden.

self.zeiger zeigt immer auf den aktuellen Datensatz
self.maxzeiger ist die höchstmögliche zeigerposition (maxzeiger ändert sich natülich wenn Datensätze angefügt oder gelöscht werden, darum arbeite ich da in der Methode nicht mit len(tabelle).

Code: Alles auswählen

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

import pickle

class Tabelle():
	
	def __init__ (self, tabelle):
		self.tabelle = tabelle
		self.zeiger = -1
		self.maxzeiger = len(self.tabelle)
		
	def gebe_naechsten_Datensatz(self):
		if self.zeiger == self.maxzeiger:
			return self.tabelle[self.zeiger][:]
		self.zeiger +=1
		if self.tabelle[self.zeiger][13] != "x":
			gebe_naechsten_Datensatz()
		else:
			return self.tabelle[self.zeiger][:]

	def gebe_vorherigen_Datensatz(self):
		pass

# Hauptprogramm

# lade Tabelle
dateiname = "videos.dmp"
with open(dateiname, "rb") as inhalt:
    tabelle = pickle.load(inhalt)

# erzeuge eine Instanz der Klasse Tabelle
tabelle = Tabelle(tabelle)

a = True
while a:
	satz = tabelle.gebe_naechsten_Datensatz()
	print (satz[0]+satz[13]) # Test-Ausgabe zu Kontrollzwecken 
	if input ("Weiter ?") != "":
	        a = False
		
Es geht mir nicht darum wie man diese Methode kürzer schreiben kann, bin ja fast sicher dass hier einige das in einem Zweizeiler hinbekommen :D , das will ich mir aber selbst erarbeiten und nicht stupide kopieren. Es geht nur ums Grundsätzliche: Die "zeiger" Variable und ob man so in Klassen arbeitet.

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

Bitte verwende doch vier Leerzeichen als Einrückung und keine Tabulatoren! In einem guten Editor kann man einstellen, dass und wieviele Leerzeichen der Editor anstelle des Tabulator-Zeichens verwenden soll.

@Code: hui... also imho ist das ziemlich... unpythonisch! Du baust da eine Art Container-Klasse, die aber imho gar keinen Sinn macht, da die kein hübscheres / sinnvolleres API bietet, als eine normale Liste! Und ein Container ohne Iterable-Interface ist einfach nicht wirklich zu gebrauche, weil Du dann keine gewohnten Zugriffstechniken wie for-Schleifen, Generatoren usw benutzen kannst. Vermutlich weißt Du als Anfänger nicht, was es alles damit auf sich hat, Du solltest da aber einfach auf die Ratschläge hier vertrauen ;-)

Vermutlich hast Du mit den grundlegenden Datenstrukturen a la Liste, Dictionaries und Sets nocht nicht gearbeitet; sonst wäre Dir schon aufgefallen, dass Du keinen Code a la

Code: Alles auswählen

table = Tabelle()

for row in table:
    # do something with row
schreiben kannst. Doch genau das will man aber ;-)

Für das Filtern von Datensätzen reichte auch eine einfache Funktion aus.

Um es mal kurz und knapp zu sagen: Klassen sind eben kein Selbstzweck. Vielleicht kenne ich Dein Problem auch zu wenig und dafür würde es sich schon lohnen Klassen einzusetzen, aber für das gezeigte lohnt es sicherlich nicht.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Sirius3
User
Beiträge: 18314
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo Tom1968,
tom1968 hat geschrieben:(klar kann man, funktioniert ja :D )
Seh ich so nicht. Es sei denn »gebe_naechsten_Datensatz« soll alle markierten Datensätze zurückgeben, außer falls der markierte Datensatz auf einen unmarkierten folgt; dann soll None zurückgegeben werden. Das Ende der Liste wird mit einem IndexError angezeigt. Um es spannender zu machen, brechen wir die Funktion hin und wieder mit einem RuntimeError ab (das fehlende »self« habe ich mal stillschweigend ergänzt).

Explizit mit Zeigern arbeitet man in Python sehr, sehr selten. Um Datensätze auszuwählen filtert man die Liste, entweder mit einem Generator, oder man erzeugt eine neue Liste, die nur die ausgewählten Daten enthält. In Deinem Fall würde man, wenn man eine eigene Tabellen-Klasse will, diese von »list« ableiten und um »filter_by_...«-Methoden erweitern.

Deine x-Feld-Methode hat den Nachteil, dass es ein zusätzliches Feld braucht, Du keine Filter-Kriterien parallel anwenden kannst, Du die Datensätze dabei veränderst und Du zweimal durch alle Datensätze durchgehen mußt, weil markieren und filtern zwei getrennte Aktionen sind.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Bitte verwende doch vier Leerzeichen als Einrückung und keine Tabulatoren! ...
Hast recht, wird gemacht.
Vermutlich hast Du mit den grundlegenden Datenstrukturen a la Liste, Dictionaries und Sets nocht nicht gearbeitet; sonst wäre Dir schon aufgefallen, dass Du keinen Code a la

Code: Alles auswählen

table = Tabelle()

for row in table:
    # do something with row
Mag, sein aber bisher sehe ich noch keinen rechten Sinn in so einer Schleife, aber das wird vielleicht am Ende dieses Postings klarer...;)
(das fehlende »self« habe ich mal stillschweigend ergänzt).
Könntest Du mir bitte sagen wo noch ein "self" fehlt ?
Es sei denn »gebe_naechsten_Datensatz« soll alle markierten Datensätze zurückgeben, außer falls der markierte Datensatz auf einen unmarkierten folgt; dann soll None zurückgegeben werden. Das Ende der Liste wird mit einem IndexError angezeigt. Um es spannender zu machen, brechen wir die Funktion hin und wieder mit einem RuntimeError ab (das fehlende »self« habe ich mal stillschweigend ergänzt)
Fast, es sollen alle markeirten Datensätze zurückgegeben werden, wenn das Ende der Liste erreicht ist wird einfach immer weder der letzte Datensatz zurückgegeben ("Non" oder ein Zurückspringen zum ersten Datensatz wäre auch eine Möglichkeit)


Es wäre vielleicht klug gewesen zuerst zu beschreiben was genau ich mit dieser Klasse anstellen will. Ich hol das mal nach:
Die Klasse Tabelle soll alle direkten Manipulationen und Zugriffe auf die 2 dimensionale Feldvariable „tabelle“ übernehmen.
Folgende Methoden sind dafür angedacht:
- Daten speichern
- Datensätze der Reihe nach an den Aufrufer geben (um dort ausgegeben oder verarbeitet zu werden) dazu soll die Datenliste vorwärts und rückwärts abgearbeitet werden z.B.
1. Aufruf: Gib den ersten Datensatz zurück
2. Aufruf: Gib den 2. Datensatz zurück
u.s.w vorwärts wie rückwärts.
- Geänderte Datensätze wieder in die Tabelle einbauen
- Datensätze löschen
- Neue Datensätze anfügen
- In den Datensätzen nach Schlüsselwörtern suchen und diese Datensätze markieren
- All das soll die Klasse mit unterschiedlichsten Tabellenformaten (Spalte, Breite) umsetzen können.

All diese Dinge benötige ich immer wieder bei kleineren Datenbankprogrammen wie Videoliste, DVD Liste, Bücherliste, Joggingtagebuch u.s.w.
Und bei alle diesen Dingen wird immer das Gleiche gemacht, außer dass die Tabellen eine unterschiedliche Spaltenbreite haben.

Als Subklassen könnte man dann später noch Klassen erstellen die Berechnungen anstellen (Vergleiche etc.)

All das andere wie Menueführung (bei einer Konsolenanwendung) oder die grafische Benutzeroberfläche wird vom Hauptprogramm erledigt, die einzige Verbindung zwischen diesen beiden ist das Laden der Tabelle (von der HD), das Erstellen des Tabellenobjekts und eben die Methodenaufrufe und Rückgabewerte.
Auch Dinge wie eine selektive Ausgabe (z.B. nur Titel und ISBN einer Bücherliste erledigt das Hauptprogramm, das Tabellenobjekt gibt immer der ganzen Datensatz weiter.

Thomas
BlackJack

@tom1968: Du siehst (noch) keinen Sinn in einer Schleife der Form ``for row in table:``, hast in Deiner Beschreibung der Operationen auf der Tabelle genau diesen Fall mit drin: Datensätze der Reihe nach an den Aufrufer zu geben. Der wird das doch in aller Regel in einer Schleife machen, und genau dafür bietet die Sprache Unterstützung für Iteratoren an. Wenn man aus irgendwelchen Gründen auch eine API braucht bei der man den Iterator selbst haben möchte und von dem ohne eine Schleife den nächsten Datensatz abfragen möchte, dann geht das mit der üblichen Iterator-API doch auch:

Code: Alles auswählen

    row_iterator = iter(table)
    first_row = next(row_iterator)
Das, und damit auch die ``for``-Schleife, geht mit jedem Container-Datentyp aus den Grunddatentypen, und mit so ziemlich jedem Container-Datentyp aus anderen Modulen und Bibliotheken. Das ist also eine Erwartung die Python-Programmierer an Container-Datentypen haben. Wenn Du jetzt die gleiche Funktionalität über eine andere API zur Verfügung stellst, ist das eigenartig.

Um noch mal auf den Sinn der ``for row in table:``-Schleife zurück zu kommen: Beim suchen von Datensätzen braucht man die, denn da müssen alle Datensätze der Reihe nach untersucht werden. Beim zurückgeben der markierten Datensätze braucht man die, denn da muss man einen nach dem anderen betrachten ob er markiert ist. Beim Löschen der Markierungen das gleiche. Für das Speichern der Tabelle kann das nötig sein.

Von so einem Tabellenobjekt würden die meisten Pythonprogrammierer erwarten, dass die Grundlegenden Sequenzoperationen wie erwartet funktionieren. Also mindestens das folgende:

Code: Alles auswählen

print len(table)
for row in table:
    print row
print table[23]
table[42] = ['Max', 'Mustermann']
del table[10]
Man sollte eine API zur Verfügung stellen die von allen anderen Containern nicht ohne guten Grund abweicht. Weil andere Programmierer das erwarten, haben die ja schon Code geschrieben der Objekte mit solchem Verhalten verarbeiten kann. Den findet man in der Standardbibliothek und in Modulen von Drittanbietern. In der Standardbibliothek zum Beispiel im `itertools`-Modul, aber auch in vielen anderen Modulen.

Schau Dir noch mal mein Modul mit den Funktionen an — bis auf `load_csv()` würde ein solcher `Table`-Typ der sich an die Standard-Sequenz-API hält, mit jeder dieser Funktionen funktionieren. Einschränkung: Der Indexzugriff darf keine Kopie der Zeilen-Liste liefern, sonst geht `set_at()` nicht. Und `load_csv()` liesse sich recht einfach anpassen wenn die `Table.__init__()` die geladene 2D-Liste als Argument nehmen würde.

Brauchst Du übrigens tatsächlich einen Iterator der sowohl vorwärts als auch rückwärts laufen kann? Und falls ja, würde ich das in einem separaten Iterator-Datentyp lösen. Am besten mit einem der allgemein auf jedem Sequenztyp funktioniert. Der wäre einfach zu schreiben. Dass es so etwas nicht in der Standardbibliothek gibt, ist IMHO ein Hinweis, dass es kaum jemand braucht.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Ich habe nun im Laufe der letzten Woche den code für diese Klasse geschrieben und das funktioniert jetzt auch (fast) alles.
Ganz bewußt habe ich den Code aber so geschrieben wie ich ihn am besten verstehe, also manchen Tipp hier in diesem Thread noch nicht beherzigt, aber das kommt noch, eine höhere Priorität als "pythonisch und funktionierend" hat bei mir erstmal "von mir verstanden und funktionierend"
Und wie erwartet erkenne ich langsam die vorteile von OOP, hier zum Besipiel die Tatsache dass ich ohne eine Änderung an der Klasse eben mit 2 Tabellen gleichzeitig arbeiten kann (2 Objekte) - eine Sache an die ich erst gar nicht dachte und die ich kaum benötigen werde, aber wer weiß ....

Eines funktioniert abrer noch nicht:
Ich will eine Suchenmethode einbauen, diese funktioniert aber bisher nur wenn in der Klasse per integerzahl die Spaltenanzahl bekannt ist, also so (es geht um die 13):

Code: Alles auswählen

for i in range (0, self.maxzeiger+1):
    self.tabelle [i][13] = "n"
    for j in range (0,13 + 1):
"row" will ich nicht verwenden um mir die Möglichkeit zu lassen nur in bestimmten Spalten zu suchen.

das angedachte sieht dann so aus:

Code: Alles auswählen

def suche_in_Tabelle(self, wort, spaltenanfang = 0, spaltenende = len (self.tabelle [0])):
     
    # Der folgende Block soll den Index zurücksetzen falls nach "" gesucht wird (Reset)
    if wort == "":    
        self.index_bei_allen_ein()
        self.zeiger = 0
        return 
    
    # Ab hier soll in allen zeilen, von Spalte 'spaltenanfang' bis Spalte 'spaltenende' gesucht werden    
    for i in range (0, self.maxzeiger+1):         
        self.tabelle [i][spaltenende] = "n"
        for j in range (spaltenanfang, spaltenende + 1):
	    if wort in self.tabelle [i][j]:
	         self.tabelle[i][spaltenende] ="x"

    # Der Block ab hier dient nur dazu den Zeiger auf den ersten gefundenen Datensatz zu setzen    
    # Das könnte man aber auch eleganter machen indem man die Tabelle im Block zuvor von hinten nach vorne
    # durchsucht und einfach den zeiger immer dann neu setzt wenn eine Übereinstimmung gefunden wird
    # damit stünde der Zeiger am Ende auch an der Zeile mit der niedrigsten Zeilennummer und einer Übereinstimmung
    # wird also noch geändert
    for i in range (0,self.maxzeiger+1):         
        if self.tabelle[i][spaltenende] == "x":   
            self.zeiger = i                               
            return
    self.zeiger = 0
    return
und genau hier bekomme ich eine Fehlermeldung:
Traceback (most recent call last):
File "Klasseversuch", line 6, in <module>
class Tabelle():
File "Klasseversuch", line 19, in Tabelle
def suche_in_Tabelle(self, wort, spaltenanfang = 0, spaltenende = len (self.tabelle [0])):
NameError: name 'self' is not defined
dies hier:

Code: Alles auswählen

def tabellen_Info_Zeilen_Spalten(self):
    return len (self.tabelle), len (self.tabelle [0])
funktioniert aber ohne Fehlermeldung, an "len (self.tabelle [0])" an sich kann es also nicht liegen, eher an der Position um Methodenkopf ?
BlackJack

@tom1968: Default-Werte werden ausgewertet wenn die ``def``-Anweisung ausgeführt wird und *nicht* jedes mal wenn die Funktion oder Methode ausgeführt wird. Zu dem Zeitpunkt wo die Methode definiert wird kann es ja noch gar keine Exemplare von dem Typ geben, also auch noch nichts was an `self` gebunden sein kann.

Das ist letztendlich aber sowieso egal, weil man Listen mit negativen Indizes von hinten ansprechen kann. Setz den Default-Wert eingfach auf -1.

Die API ist echt gruselig. Neben dieser ganzen unnötigen Indexerei ist die versteckte Reset-Funktion unschön. Wenn eine Methode bei allen Werten das macht was der Name suggeriert, und bei *einem* speziellen Wert etwas *völlig anderes*, dann ist das nicht wirklich nachvollziehbar.

`self.maxzeiger` ist redundant. Ich gehe mal davon aus, dass das bei Dir kein `property()` ist. Warum schleppst Du diese unnötige Information mit, die *immer* den gleichen Wert hat wie ``len(self.tabelle)``, beziehungsweise die mit Sicherheit falsch ist, wenn die beiden Werte nicht übereinstimmen‽

Ich verstehe echt nicht warum Du das alles so kompliziert machst…
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

@tom1968: Default-Werte werden ausgewertet wenn die ``def``-Anweisung ausgeführt wird und *nicht* jedes mal wenn die Funktion oder Methode ausgeführt wird. Zu dem Zeitpunkt wo die Methode definiert wird kann es ja noch gar keine Exemplare von dem Typ geben, also auch noch nichts was an `self` gebunden sein kann.
Das ist letztendlich aber sowieso egal, weil man Listen mit negativen Indizes von hinten ansprechen kann. Setz den Default-Wert eingfach auf -1.
Super, Danke
Die API ist echt gruselig. Neben dieser ganzen unnötigen Indexerei ist die versteckte Reset-Funktion unschön. Wenn eine Methode bei allen Werten das macht was der Name suggeriert, und bei *einem* speziellen Wert etwas *völlig anderes*, dann ist das nicht wirklich nachvollziehbar.
Sei froh dass Du dir den Rest nicht ansehen must :D
Im Ernst, ich habe mir schon was dabei gedacht, diese versteckte Reset Funktion dient dazu dass man damit bei einer tkinter Oberfläche in das Suchen Textfeld einfach nichts eingibt, Return drückt und wieder alles angezeigt wird (ähnlich wie die Suchen Funktion im Win7 Explorer) - aber gut, das könnte man natürlich auch im Frontend erledigen...
`self.maxzeiger` ist redundant. Ich gehe mal davon aus, dass das bei Dir kein `property()` ist. Warum schleppst Du diese unnötige Information mit, die *immer* den gleichen Wert hat wie ``len(self.tabelle)``, beziehungsweise die mit Sicherheit falsch ist, wenn die beiden Werte nicht übereinstimmen‽
self.maxzeiger ist die Zeilenanzahl und dient dazu dass mir der Zeiger nicht über die Tabelle hinauswandern kann (Das per try und except zu machen gefällt mir nicht)
len (self.tabelle [0]) gibt mir die Spaltenanzahl zurück, was ich brauche um nur bestimmte Bereiche (Spalten) der Tabelle zu durchsuchen - meine Idee ist die dass wenn ich da keine Argumente übergebe tabelle.suche_in_Tabelle(Wort) alle Spalten durchsucht werden,
mache ich es per tabelle.suche_in_Tabelle(Wort, 0, 3) werden nur die ersten 4 Spalten durchsucht.
Sirius3
User
Beiträge: 18314
Registriert: Sonntag 21. Oktober 2012, 17:20

@BlackJack: Dein Tipp mit default »spaltenende=-1« schlägt bei »range(spaltenanfang, spaltenende+1)« fehl.

@tom1968: auch ich kann hier nicht ruhigen Gewissens mitlesen. Listenverarbeitung ist eine der großen Stärken von Python, und damit sind Probleme, die Du hier versucht zu lösen, in einer Zeile erledigt:

Code: Alles auswählen

selected_rows = [row for row in table if any(wort in cell for cell in row)]
Die Funktion »range« wird so gut wie nie gebraucht und Anfänger benutzen sie immer an der falschen Stelle.
Du willst alle Elemente Deiner Tabelle durchgehen:

Code: Alles auswählen

for row in self.table:
    for cell in row[column_begin:column_end]:
        …
Und hier taucht Dein nächstes Problem auf: In Python ist Konvention, dass das letzte Element immer nicht mitgenommen wird. Dein »spaltenende« verstößt gegen diese Konvention und wird jeden Nutzer verwirren. Würde Dein jetziger Code funktionieren, würde er auch eine Spalte zu weit zählen und mit einem IndexError abbrechen. Zum zweiten würde Deine Marker-Spalte defaultmäßig auch auf »wort« geprüft, was rein logisch keinen Sinn ergibt.

Nächster Fehler: Deine Marker-Spalte hängt von »spaltenende« ab. Wenn also nicht alle Spalten durchsucht werden, überschreibst Du Spalten.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

@Sirius3
Ich denke Du hast recht, werde Deinen Code mal testen....
Und hier taucht Dein nächstes Problem auf: In Python ist Konvention, dass das letzte Element immer nicht mitgenommen wird.....
Ja ich weiß, daher auch das "+1" am Ende, finde ich auch nicht schön
Zum zweiten würde Deine Marker-Spalte defaultmäßig auch auf »wort« geprüft, was rein logisch keinen Sinn ergibt.
Dessen war ich mir schon bewußt, könnte ich noch ändern, wenn es so bliebe würde es aber auch nichts machen, es sei denn man sucht nach einem "n" oder "x" was sehr unwahrscheinlich ist, kostet also nur Rechenzeit - was unschön aber auch nicht wirklch dramatisch ist.
Nächster Fehler: Deine Marker-Spalte hängt von »spaltenende« ab. Wenn also nicht alle Spalten durchsucht werden, überschreibst Du Spalten.
Oh ja, da hast Du recht, ich testete das Ganze bisher nur mit dem fest eingestellten Wert von 13, da funktioniert es auch, das mit der begrenzten suche nur in bestimmten Spalten fügte ich erst heute ein, testen konnte ich es wegen der oben beschriebenen Fehlermeldung nicht.
BlackJack

@tom1968: Nach 'n' oder 'x' zu suchen ist unwahrscheinlich? Du kannst Dir also nicht vorstellen, das jemand eine Spalte hat die 'j' oder 'n' für Ja und Nein enthält? Oder '.' und 'x' für etwas Ähnliches? Und um Rechenzeit musst Du Dir echt keine Sorgen machen. Davon verbrätst Du eh schon genug. Ob eine Datensatz markiert ist oder nicht ist tabelleninterner Verwaltungskram. Der sollte in den Daten eigentlich sowieso nichts zu suchen haben. Das wäre etwas für eine Attribut zusätzlich zu den Daten in einem eigenen Datentyp für einzelne Datensätze. Die Wahl von 'x' und 'n' finde ich auch ein wenig komisch. 'j'/'n', 't'/'f', 'y'/'n' okay, aber 'x'/'n'?

Und beim `self.maxzeiger` verstehe ich Deine Argumentation nicht. Wozu brauchst Du den? Warum nicht einfach ``len(self.tabelle)``? Das sollte immer den gleichen Wert liefern wie `self.maxzeiger`. Ohne dass Du das noch einmal parallel verwalten musst. Listen kennen ihre eigene Länge. Wobei auch Ausnahmen hier nicht unbedingt schlecht sind, weil das ja tatsächlich nur in einer Ausnahmesituation zutrifft. Letztendlich ist aber diese ganze „Zeiger”-Idee unnötig umständlich.

Deine gesamte Methode könnte ungefähr so aussehen (ungetestet):

Code: Alles auswählen

    def suchen(self, wort, spalten_anfang=0, spalten_ende=-2, marker_spalte=-1):
        self.zeiger = None
        for i, datensatz in enumerate(self.tabelle):
            gefunden = any(
                wort in zelle for zelle in datensatz[spalten_anfang:spalten_ende]
            )
            datensatz[marker_spalte] = 'j' if gefunden else 'n'
            if gefunden and self.zeiger is None:
                self.zeiger = i
Dann hätte man natürlich immer noch dieses „Zeiger”-Konzept, was komisch ist.

Ist aber auch immer noch etwas unflexibel. Ich würde eine Methode zur Verfügung stellen, die ein aufrufbares Objekt entgegen nimmt, was mit jedem Datensatz aufgerufen wird und entscheidet ob der zum Ergebnis gehört oder nicht. Quasi eine Methode wie die `filter()`-Funktion.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Danke BlackJack, habe Deinen Code nun mal in meine Klasse reinkopiert - und es funktioniert (ohne jede Änderung)
Und die Methode hat nun 6 statt wie vorher 22 Zeilen :o

Ich werde nun die anderen Methoden auch umbauen, denn zugegebenermaßen sieht das so wie von Dir vorgeschlagen sauberer aus und wird wohl auch leichter wartbar sein.

Den self.maxzeiger dachte ich mir deshalb aus weil ich den bei meinem Code relativ häufig brauche (wegen meiner häufigen Verwendung von "for ...range" - und ich vermutete dass das performancetechnisch schneller geht wenn hier nicht jedesmal mit len gerarbeitet wird ?!
Ist aber egal, dank Deines Beispiels mit enumerate versuche ich range soweit möglich zu ersetzen und brauche dann vermutlich weder den maxzeiger noch len, der maxzeiger fliegt also raus :D

.... Ich würde eine Methode zur Verfügung stellen, die ein aufrufbares Objekt entgegen nimmt, was mit jedem Datensatz aufgerufen wird und entscheidet ob der zum Ergebnis gehört oder nicht. Quasi eine Methode wie die `filter()`-Funktion.
Könntest Du mir das bitte mit einfachen Worten oder anhand eines kurzen Codebeispiels nochmal erklärten, ich verstehe nicht was Du meinst.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

tom1968 hat geschrieben:
.... Ich würde eine Methode zur Verfügung stellen, die ein aufrufbares Objekt entgegen nimmt, was mit jedem Datensatz aufgerufen wird und entscheidet ob der zum Ergebnis gehört oder nicht. Quasi eine Methode wie die `filter()`-Funktion.
Könntest Du mir das bitte mit einfachen Worten oder anhand eines kurzen Codebeispiels nochmal erklärten, ich verstehe nicht was Du meinst.
In ganz grundlegendem Python und einem Prädikat (Funktion die Wahrheitswerte ausgibt) das prüft ob das übergebene Element dem String ``"green"`` entspricht:

Code: Alles auswählen

def is_green(element):
    return element == "green"

def my_filter(fn, lat):
    matched = []
    for e in lat:
        if fn(e):
            matched.append(e)

ele = ["green", "red", "blue", "green"]
``my_filter`` kann man etwas optimieren:

Code: Alles auswählen

def my_filter(fn, lat):
    return [e for e in lat if fn(e)]
Und genau das macht die eingebaute Funktion ``filter``, also braucht man ``my_filter`` gar nicht. Hoffe das hilft etwas.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

(Achtung: Python 2.x)

Code: Alles auswählen

import csv
import sys
from functools import partial
from itertools import ifilter


def any_column_contains(value, row):
    return any(value in cell for cell in row)


class Table(object):
    def __init__(self, rows=()):
        self.rows = list(rows)

    def __len__(self):
        return len(self.rows)

    def __getitem__(self, index):
        return self.rows[index]

    def filtered(self, predicate):
        return Table(ifilter(predicate, self))

    def write_csv(self, out_file, delimiter=';'):
        csv.writer(out_file, delimiter=delimiter).writerows(self)

    @classmethod
    def load_csv(cls, filename, delimiter=';'):
        with open(filename) as csv_file:
            return cls(csv.reader(csv_file, delimiter=delimiter))


def main():
    table = Table.load_csv('test.csv')
    table.write_csv(sys.stdout)
    print
    view = table.filtered(partial(any_column_contains, 'Muster'))
    view.write_csv(sys.stdout)


if __name__ == '__main__':
    main()
Ausgabe:

Code: Alles auswählen

Max;Mustermann;München
Melanie;Musterfrau;Köln
Angela;Merkel;Berlin

Max;Mustermann;München
Melanie;Musterfrau;Köln
Sirius3
User
Beiträge: 18314
Registriert: Sonntag 21. Oktober 2012, 17:20

Jetzt erben wir noch direkt von »list« und nehmen die dann überflüssigen Methoden raus.
Die Asymmetrie zwischen »load_csv« und »write_csv« ist auch unschön.
Ich hätte ja persönlich eine Filterklasse benutzt:

Code: Alles auswählen

import csv
import sys


class AnyColumnContainsFilter(object):
    def __init__(self, value, first_column=0, last_column=None):
        self.value = value
        self.first_column = first_column
        self.last_column = last_column

    def __call__(self, row):
        return any(self.value in cell for cell in row[self.first_column:self.last_column])


class Table(list):
    def filtered(self, predicate):
        return Table(row for row in self if predicate(row))

    def write_csv(self, out_file, delimiter=';'):
        csv.writer(out_file, delimiter=delimiter).writerows(self)

    @classmethod
    def load_csv(cls, csv_file, delimiter=';'):
        return cls(csv.reader(csv_file, delimiter=delimiter))


def main():
    with open('test.csv') as csv_file:
        table = Table.load_csv(csv_file)
    table.write_csv(sys.stdout)
    print
    view = table.filtered(AnyColumnContainsFilter('Muster'))
    view.write_csv(sys.stdout)


if __name__ == '__main__':
    main()
BlackJack

@Sirius3: Erben von eingebauten Datentypen finde ich in der Regel nicht schön weil man da, nun ja, alles von dem Typ erbt. Oft auch vieles was man gar nicht haben möchte, oder die Benutzer sind dann enttäuscht, dass bei einigen Operationen wo sie den neuen Typ erwarten, dann doch wieder der Basistyp zurückgegeben wird.

Die Assymmetrie beim lesen und schreiben von CSV ist da weil es ein minimales Beispiel bleiben sollte. Ausgebaut hätte es natürlich `read()`/`write()` gegeben und darauf aufbauende `load()`/`save()` Methoden. Nur das ich `read()` und `save()` für das Beispiel nicht gebraucht habe, und das ja auch ums Filtern ging.

Ich hätte wohl auch eine Klasse für den Filter geschrieben, aber der OP scheint ja damit eher sparsam umgehen zu wollen. :-)
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Ich hätte wohl auch eine Klasse für den Filter geschrieben, aber der OP scheint ja damit eher sparsam umgehen zu wollen.
Ich bin froh überhaupt eine Klasse zustande gebracht zu haben, meine erste Klasse überhaupt - und werde mich da jetzt nicht in weitere Klassen verzetteln :D :D :D :D

Die Klasse steht und funktioniert, ist aber nicht besonders pythonisch :P
Gut möglich dass ich das später noch ändere, aber Priorität hat natürlich die Funktion an sich.

Ich möchte mich bei allen die sich hier in diesem Thread beteiligt haben sehr herzlich für die aufgewendete Zeit bedanken, auch wenn nicht alles umsetzbar war bzw. mir für meine Zwecke sinnvoll erschien - z.B. eine Schleife die alle markierten Datensätze zurückgibt - diese Schleife ist bei mir im Hauptprogramm - was ich für flexibler halte da die Klasse dann unabhängiger ist (Frontend - grafisch oder Konsole).

Auf jeden Fall bin ich einen Schritt weiter was das Verständnis für Klassen anbetrifft, auch wenn gerade dieses Beispiel hier wohl als Modul mindestens genauso sinnvoll gewesen wäre. Aber vielleicht will ich ja einmal mit einem Frontend an zwei Listen gleichzeitig arbeiten und dann, denke ich, macht die Klasse natürlich mehr Sinn als ein Modul.
Und ich habe die tollen Möglichkeiten der for Schleife ohne range begriffen, was ich bisher eher für eine Spielerei :D hielt.

Also, nochmal Danke für eure Hilfe !!!
Thomas
BlackJack

@tom1968: Die Schleife im Hauptprogramm ist nicht flexibler, sondern schränkt eher ein. Dazu muss Dein Hauptprogramm nämlich wissen wie die Tabelle intern aufgebaut ist, also kann man das nicht mehr so einfach ändern ohne dass man den Code der die Tabelle benutzt auch ändern muss. Genau so etwas versucht man zu verhindern und eine Schnittstelle nach aussen anzubieten, welche die Interna einer Tabelle kapselt.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

O.k., "Schleife" war wohl unpräzise, ich mache es anders, aber....
Mein Ziel ist genau das von Dir beschriebene, ich will in der Klasse, für die jeweiligen 'Datenbanken' nichts verändern oder anpassen - und genau das müsste ich machen wenn die Ausgabe durch die Klasse erfolgte und wenn ich die Daten einmal in der Konsole übereinander (z.B. Bücherliste), einmal nebeneinander (z.B. Joggingtagebuch wo fast nur Zahlen enthalten sind) und einmal per tkinter auf einer grafischen Oberfläche darstellen will.
Auch wird die tabellen-variable oder der zeiger von außen nicht angerührt.

Das Ganze mal an einem Beispiel: Es wird irgendein Datensatz ausgegeben und ich möchten mir den nächsten Datensatz ansehen.

Das kann dann für eine Konsolenanwendung so aussehen (der Code wurde nur zum Testen der Klasse verwendet und wird durch die Auswahl „Ausgabe" aus einem Hauptmenue aufgerufen:

Code: Alles auswählen

def ausgabe():
    c = tabelle.gebe_aktuellen_Datensatz()
    print (c[0]+c[13])    #testweise hier nur mal zwei Spalten
    while True:
        b= input ("q = vor  w = ende  a = zurück s = anfang  ä = ändern lö = löschen ende = Hauptmenue ")
....
        if b == "q":
            c = tabelle.gebe_naechsten_Datensatz()
....
        print (c[0]+c[13])   #testweise hier nur mal zwei Spalten

Für eine grafische Oberfläche könnte dass dann so aussehen:

Code: Alles auswählen

vorwaertsButton = Button(root,width =7, text = " > ", command = ausgabe(tabelle.gebe_naechsten_Datensatz())
.....

def ausgabe(datensatz):
    textfeld1.delete(0, END)    
    textfeld.insert .....
......
Das zweite Beispiel ist natürlich nur schematisch und ich empfinde es als unschön nach der button-command Anweisung zu einer Funktion zu springen die im Grunde nur eine Klassenmethode aufruft und dann eine weitere Funktion - aber was besseres fällt mir momentan nicht ein - zwei Aufrufe hintereinander wird mir die command Anweisung vermutlich nicht akzeptieren ?!

Der Code an sich ist zweitrangig, was ich damit darstellen will ist lediglich dass an der Klasse, egal wie das Frontend aussieht, rein gar nichts verändert werden muss, der Aufruf ist in beiden Fällen der gleiche - und dass auch nicht auf Internes der Klasse zurückgegriffen wird.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Eine andere Möglichkeit wäre es vielleicht noch Subklassen zu erstellen, in denen dann die Ausgabe für die jeweiligen „Datenbanken“ bewerkstelligt wird.

Genau betrachtet bringt das aber nichts weil diese Ausgabe bei jedem Programm anders aussehen wird, die Wiederverwendbarkeit dieser Subklassen als gleich null wäre und ich nur Code vom Hauptprogramm in die Subklassen schieben würde - was sich bei der Programmgröße nicht lohnt.
Antworten