Erstlingsproblem: self in Funktion bearbeiten?

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.
Antworten
pyron
User
Beiträge: 12
Registriert: Montag 14. März 2011, 09:33
Wohnort: Ratingen
Kontaktdaten:

Hallo zusammen,
ich habe als Einstieg ein kleines Programm geschrieben, welches mir ein Objekt nach vorher definierten Kriterien ausgeben soll. Ist im Suchbereich keines dieser Objekte verfügbar, soll das Suchobjekt geändert werden (damit der Suchbereich sich verändert).
Wenn ich das Programm laufenlasse, sieht es so aus, als wäre immer nur nach dem gleichen Objekt gesucht worden. Deswegen tut sich bei mir die Frage auf, ob man ein übergebens Self-Objekt in einer Funktion nicht verändern kann...
Ich würde mich freuen, wenn mir jemand bei meiner Frage weiterhelfen könnte.

Das Programm findet ihr hier:
http://www.python-forum.de/pastebin.php?mode=view&s=171
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Willkommen im Forum!

Ich pick das mal exemplarisch raus

Code: Alles auswählen

def get_klassenliste(liste):
    '''Gibt eine Liste aller Klassen aus'''
    a=[]
    i=0
    for eintrag in range(len(liste)):
        a.append(liste[i].klasse)
        i = i+1
    a=set(a)
    b=list(a)
    b.sort()
    return b
1. `liste` wird das "self" dieser Methode. In einer Methode, d.h. einer Funktion innerhalb einer Klassendefinition, wird an den 1. Parameter immer das Exemplar gebunden.
2. Anstatt `i` koenntest du hier direkt `eintrag` nutzen und du musst nicht mehr selbst hochzaehlen und es geht noch einfacher:
3. Anstatt ueber die die Indizes zu gehen kannst du auch direkt die Eintraege nutzen, d.h.

Code: Alles auswählen

for eintrag in liste:
    a.append(eintrag.klasse)
4. Und mit List comprehensions wird es noch kuerzer.

Insgesamt dann:

Code: Alles auswählen

def get_klassenliste(self, liste):
    b = set([eintrag.klasse for eintrag in liste])
    return sorted(list(b))
BlackJack

@pyron: Namen sollte nach Möglichkeit keine Typen enthalten. Also zum Beispiel `get_klassenliste()` -- wenn man da den Rückgabetyp mal ändert, müsste man den Namen ändern oder man hätte einen irreführenden Namen. `get_klassen()` ist ausreichend. Die "Methode" hat IMHO auf `Lagerplatz` nichts verloren. Es ist ja nicht einmal eine Methode sondern einfach nur eine Funktion. Wenn es weder die Klasse noch ein übergebenes Exemplar davon benötigt, dann sollte man es als Funktion schreiben, oder zumindest mit `staticmethod()` klar machen, dass es keine Methode ist, sondern eine Funktion die thematisch mit der Klasse zu tun hat.

Die `Lagerplatz.__str__()` ist auch deutlich zu kompliziert. Das ist ein Einzeiler:

Code: Alles auswählen

    def __str__(self):
        return str([self.nummer, self.empty, self.klasse, self.variante])
Einen Docstring würde ich solchen "magischen" Methoden nur verpassen, wenn da Informationen drin stehen, die man sonst nicht hätte. Was die `__str__()` Grundsätzlich tut, sollte man als bekannt voraussetzen dürfen.

Abkürzungen sollte man vermeiden, insbesondere wenn es sich um welche handelt, die nicht allgemein bekannt sind. `sucheLP()` sollte `suche_lagerplatz()` heissen. Es sei denn die Methode sucht tatsächlich Langspielplatten. Ebenso sind einbuchstabige Namen ausserhalb von Mathematik-Problemen und Zählvariablen in Schleifen unschön. Sehr kurze lokale Namen in "list comprehensions" und Generator-Ausdrücken sind auch noch okay, weil da der Einsatzbereich eng genug gefasst ist, dass man nicht den Überblick verliert. Ebenfalls nicht schön sind generische Namen wie `list` oder `liste` wenn man viel erklärendere Namen vergeben könnte. Die `list` in `sucheLP()` würde zum Beispiel besser `lagerplaetze` heissen. Dann weiss man auf den ersten Blick was darin gespeichert ist.

In `sucheLP()` wird auf das globale `Posten` zugegriffen: Das ist total unsauber und macht Quelltext schwer überschaubar. Und in diesem Fall würde ich sogar sagen, dass das ein Programmfehler ist. Werte die innerhalb einer Methode benutzt werden, sollten vom Objekt oder über die Argumentliste kommen. Auf Modulebene kann man auf Konstanten zugreifen, aber nicht irgendwelche Veränderungen an modulglobalen Objekten vornehmen. Die Schleifen scheinen mir auch ziemlich umständlich. Filtern der Liste nach in Frage kommenden Lagerplätzen und davon dann das Minimum von Klasse/Variante zu suchen erscheint mir irgendwie viel einfacher!? Ungetestet:

Code: Alles auswählen

    def suche_lagerplatz(self, lagerplaetze):
        # 
        # Belegte und zu kleine Lagerplätze ausfiltern.
        # 
        kandidaten = (
            lp for lp in lagerplaetze
            if lp.empty
                and lp.klasse >= self.klasse
                and lp.variante >= self.variante
        )
        # 
        # Kleinsten möglichen Lagerplatz zurück geben.
        # 
        try:
            return min(kandidaten, key=lambda lp: (lp.klasse, lp.variante))
        except ValueError:
            raise ValueError('Keinen Lagerplatz gefunden.')
Hier wird auch nicht `None` zurück gegeben, sondern ein `ValueError` ausgelöst, wenn kein passender Lagerplatz gefunden wird. Ich gehe ausserdem davon aus, dass `empty` ein Wahrheitswert sein sollte und keine Zeichenkette mit einem Text, der einen Wahrheitswert darstellt.

`sucheLP()` und `findeLP()` sind mir in der Unterscheidung des Namens zu subtil. Da sollte man bessere Namen finden, so dass man nicht raten muss, was denn der Unterschied zwischen den beiden ist.

Vom Entwurf her sind IMHO einige Methoden auf den falschen Objekten. Die Klassen und Varianten aus einer Liste zu suchen, sollte nicht Aufgabe eine `Lagerplatz`\es sein, genau so wenig wie das suchen eines passenden `Lagerplatz`\es in einer Liste zum `Artikelposten` gehört. Beides wären Aufgaben einer Klasse die `Lagerplatz`-Objekte enthält und `Artikelposten` dort ablegen und heraus holen kann. Ausgaben an den Benutzer gehören in solche Programmlogik-Klassen auch nicht hinein.

Das Hauptprogramm sollte in einer Funktion verschwinden. Dann können solche Fehler wie der Zugriff auf das globale `Posten` aus einer Methode heraus auch gar nicht erst passieren.

Die Liste im Hauptprogramm wird wieder reichlich umständlich erstellt. Die Namen `Bamler10` bis `Bamler20` sind unnötig.

Edit: Vielleicht noch als Ergänzung: Wenn als "natürliche" Ordnung von Lagerplätzen die Klasse und Variante in Frage kommen, könnte man `Lagerplatz`-Objekte auch auf grösser/kleiner/gleich vergleichbar machen und sich damit die `key`-Funktion bei `min()` sparen. Der Test ob ein Posten in einen Lagerplatz passt, könnte man auch auf den Lagerplatz verlegen. Wenn man dass dann noch auf eine Container-Klasse verschiebt, die Lagerplätze verwaltet, könnte das letztendlich so aussehen:

Code: Alles auswählen

    def _suche_lagerplatz(self, posten):
        kandidaten = (lp for lp in self if lp.could_contain(posten))
        try:
            return min(kandidaten)
        except ValueError:
            raise ValueError('Keinen Lagerplatz gefunden.')
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

cofi hat geschrieben:

Code: Alles auswählen

def get_klassenliste(self, liste):
    b = set([eintrag.klasse for eintrag in liste])
    return sorted(list(b))
Die zwei Listen sind unnötig (ungetestet):

Code: Alles auswählen

def get_klassenliste(self, liste):
    b = set(eintrag.klasse for eintrag in liste)
    return sorted(b)
Dann ist eigentlich auch folgendes nicht mehr schwierig zu verstehen:

Code: Alles auswählen

def get_klassenliste(self, liste):
    return sorted(set(eintrag.klasse for eintrag in liste))
pyron
User
Beiträge: 12
Registriert: Montag 14. März 2011, 09:33
Wohnort: Ratingen
Kontaktdaten:

Hallo BlackJack,

vielen Dank für deine tiefgehende Antwort, ich habe viel dadurch über List Comprehensions und Exceptions gelernt.
Es tut mir leid, dass ich so schlechte Objektnamen gewählt habe, dies war mein erstes "objektorientiertes Programm".

Ich habe aber noch ein paar Fragen bzgl. deiner Antwort, und würde mich freuen, wenn du diese beantworten könntest:

get_klassenliste, war einfach eine Sache die ich schreiben wollte, wie soll ich diese "bündeln" wenn nicht in einer Klasse, ich bin da vielleicht etwas unbedacht, aber was ist der Unterschied zwischen einer Methode und einer Funktion?

Die Idee, das Minimum von Klasse / Variante zu suchen finde ich toll, da wäre ich nie drauf gekommen. Werde das gleich einbauen und ausprobieren.

Welcher "Klasse" würde ich denn am besten diese Suche und Finde Methoden anhängen; logischer Natur?

Wie soll man Ausgaben an den Benutzer in eine andere Klasse fassen; ich wüsste jetzt nicht wie und welche Bewandnis dies hat?

Was wäre denn eine Container-Klasse bzw. wie müsste ich diese gestalten?


Ich entschuldige mich schon im Voraus für meine Anfängerfragen, leider bin ich kein Programmierer oder kenne keinen den ich fragen kann...
BlackJack

@pyron: Eine Klasse fasst, ganz grob gesagt, Daten und Funktionen die darauf operieren zu einer "Einheit" zusammen. Die Funktionen nennt man dann Methoden. Wenn eine Funktion aber gar nicht auf den Daten operiert, die zu dem Objekt gehören, technisch gesehen: wenn das `self`-Argument nicht benötigt wird, ist es nur formal eine Methode. Könnte aber genau so gut als Funktion ausserhalb der Klasse geschrieben werden. Manchmal hat man Funktionen, die so eng mit einem Datentyp verbunden sind, dass man sie trotzdem gerne in den Namensraum der Klasse stecken möchte. Dann kann man mit `staticmethod()` eine sogenannte "statische Methode" daraus machen. Die braucht dann auch das `self` nicht als erstes Argument.

Das suchen von einem Lagerplatz würde logisch am besten zu einem `Lager` passen was Lagerplätze enthält. Also eine weitere Klasse. Das Lager wäre eine Container-Klasse. Das sind Klassen die eine Menge von anderen Objekten verwalten wie zum Beispiel Listen.

Programmlogik und Benutzerinteraktion sollte man trennen. Sonst kann man die Klassen mit der Programmlogik nicht für Aufgaben verwenden bei denen der Benutzer gar nicht, oder anders informiert werden soll.
pyron
User
Beiträge: 12
Registriert: Montag 14. März 2011, 09:33
Wohnort: Ratingen
Kontaktdaten:

Hallo Black Jack:
ich habe deine Vorschläge versucht weitestgehend zu berücksichtigen und habe mir auch folgenden Link dazu zum lernen angeschaut, den ich sehr interessant finde. http://wiki.python.org/moin/HowTo/Sorting/

Jedoch habe ich noch eine Frage:
Wenn ich das Programm ausführe, bekomme ich keine Ausgabe. Womit kann das zu tun haben? Ich bekomme keine Fehlermeldung. Wenn ich mir Kandiaten printen lassen möchte, bekomme ich nur das Generatorobjekt zurückgemeldet.

PS: Hier das überarbeitete Programm http://www.python-forum.de/pastebin.php?mode=view&s=172
Sorry dass ich die Funktionen noch nicht statisch gemacht habe.

Noch eine kleine Frage am Rande, dessen Beantwortung ich nicht finden konnte:
Wie kann man IDLE einstellen, dass die Codeeinfärbungen nach dem Ausführen eines Moduls bestehen bleiben? Bei mir ist alles nur weis.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

pyron hat geschrieben:Jedoch habe ich noch eine Frage:
Wenn ich das Programm ausführe, bekomme ich keine Ausgabe. Womit kann das zu tun haben? Ich bekomme keine Fehlermeldung. Wenn ich mir Kandiaten printen lassen möchte, bekomme ich nur das Generatorobjekt zurückgemeldet
Mal davon abgesehen, dass ich nirgends eine Anweisung zur Ausgabe sehe, ist das beschriebene Verhalten vollkommen normal. Ein Generator erzeugt erst dann ein Objekt, wenn dieses auch benötigt wird. Nach der Erzeugung wird es dann einfach vergessen. Du kannst dir daher nicht den "Inhalt" eines Generators anzeigen lassen, ohne dass du diesen aufbrauchst. Dann müsstest du auf eine Liste zurückgreifen.

Sebastian
Das Leben ist wie ein Tennisball.
pyron
User
Beiträge: 12
Registriert: Montag 14. März 2011, 09:33
Wohnort: Ratingen
Kontaktdaten:

Hallo Sebastian, vielen Dank für deinen Hinweis... ich bin aber auch ein Schussel.
Vielen Dank, habe nun gesehen, woran es gelegen hat.

Ich bin sowas von begeistert, über dieses Forum. Ich habe in so kurzer Zeit schon soviel lernen können. Tausend dank
Antworten