Denkanstoss OOP

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
mcdwerner
User
Beiträge: 113
Registriert: Donnerstag 7. Juli 2011, 14:27

Hallo!

Über die Verwendung von Python-Klassen hab ich nun schon jede Menge Tutorials und Snippets gelesen aber die eigene Umsetzung gelingt mir nicht so recht.

Nun steht der Urlaub an und wenn man sich in der Familie nicht einigen kann muss der Zufall aushelfen ;-)

Dazu hab ich mir folgendes Skript überlegt und nun denke ich mir, dass Klassen ganz nützlich wären, wenn ich das Skript noch um weitere "Attraktionen" und "Events" erweitern möchte.

Code: Alles auswählen

import random

def einzigartigeAttraktion(n):
#übernimmt 0, 1 oder 2
    Auswahl = n
    if Auswahl == 2:
        Auswahl = random.randint(0,1)
    if Auswahl:
        return 'Yippie, wir besuchen die einzigartige Attraktion.'
    else:
        return 'einzigartige Attraktion: Nö!'

def Strand1(von, bis):
#übernimmt 2 Argumente: Zufallsauswahl von bis...       
    von = von
    bis = bis
    if bis == 0:
        strand = 0
    else:
        strand = random.randint(0,1)
    if strand:
        strandreturn = 'Wir gehen ' + str(random.randint(von, bis)) + ' mal an Strand 1.'
    else:
        strandreturn = 'Strand 1: Nö!'
    return strandreturn

def Segeln(von, bis):
    von = von
    bis = bis
    if bis == 0:
        segeln = 0
    else:
        segeln = random.randint(0,1)
    if segeln:
        return random.randint(von, bis)
    else:
        return 0

def Strand2(von, bis):
#nur hier kann man segeln...
    von = von
    bis = bis
    if von == 'Segeln':
        von = Segeln(1,5)
        bis = von + bis
        strand = 2
    else:
        if bis == 0:
            strand = 0
        else: strand = random.randint(0,1)
    if strand == 1:
        strandreturn = 'Wir gehen ' + str(random.randint(von, bis)) + ' mal an Strand 2, ohne Segeln.'
    elif strand == 0:
        strandreturn = 'Strand 2: Nö!'
    elif strand == 2:
        strandreturn = 'Wir gehen ' + str(random.randint(von, bis)) + ' mal an Strand 2, davon' + str(von) + ' mal  Segeln.'
    return strandreturn
Leider finde ich keinen rechten Einstieg Richtung Klassenverwendung, hat einer von Euch einen Denkanstoß für mich?

Vielen Dank schonmal und beste Grüße,
Werner
BlackJack

@mcdwerner: Also ich verstehe den Quelltext mit den Funktionen schon nicht wirklich, darum ist es schwierig den mit Klassen zu modellieren.

Ein Grund warum der Quelltext schwer zu verstehe ist, sind die vielen magischen Zahlen, die da herum schwirren. Zum Beispiel bei der `einzigartigeAttraktion()`-Funktion muss man die Funktion selbst lesen um zu wissen was das Argument `n` bedeutet.

Ähnliches gilt für `von` und `bis` bei den anderen Funktionen. Da war meine erste Vermutung beim Lesen der Signaturen es könnten Zeiten sein. Da war ich jetzt zu faul den Quelltext zu lesen und zu raten was sie wirklich bedeuten.

Warum bindest Du als aller erstes in den Funktionen eigentlich die Argumente noch einmal an die gleichen Namen? Das hat keinen Effekt.

Funktionsnamen tun etwas, deshalb sollten sie nicht Namen von Dingen sondern von Tätigkeiten haben. Und diese Namen sollten beschreiben, was die Funktionen tun.

Mir scheint die Funktionsnamen kodieren bei Dir eigentlich Daten. Zum Beispiel Ausflugsziele und Tätigkeiten, die man dort ausführen kann. Vielleicht solltest Du das eher in eine Datenstruktur stecken, die zum Beispiel einen Baum mit Entscheidung@mcdwerner: Also ich verstehe den Quelltext mit den Funktionen schon nicht wirklich, darum ist es schwierig den mit Klassen zu modellieren.

Ein Grund warum der Quelltext schwer zu verstehe ist, sind die vielen magischen Zahlen, die da herum schwirren. Zum Beispiel bei der `einzigartigeAttraktion()`-Funktion muss man die Funktion selbst lesen um zu wissen was das Argument `n` bedeutet.

Ähnliches gilt für `von` und `bis` bei den anderen Funktionen. Da war meine erste Vermutung beim Lesen der Signaturen es könnten Zeiten sein. Da war ich jetzt zu faul den Quelltext zu lesen und zu raten was sie wirklich bedeuten.

Warum bindest Du als aller erstes in den Funktionen eigentlich die Argumente noch einmal an die gleichen Namen? Das hat keinen Effekt.

Funktionsnamen tun etwas, deshalb sollten sie nicht Namen von Dingen sondern von Tätigkeiten haben. Und diese Namen sollten beschreiben, was die Funktionen tun.

Mir scheint die Funktionsnamen kodieren bei Dir eigentlich Daten. Zum Beispiel Ausflugsziele und Tätigkeiten, die man dort ausführen kann. Vielleicht solltest Du das eher in eine Datenstruktur stecken, die zum Beispiel einen Baum mit Entscheidungsmöglichkeiten modelliert. *Das* könnte man dann zum Beispiel auch mit Klassen für die einzelnen Knoten in dem Baum machen. Dein Beispiel hätte dann drei Knoten in der ersten Ebene und beim zweiten Strand noch zwei weitere Auswahlmöglichkeiten.

Code: Alles auswählen

Attraktion
Strand 1
Strand 2
    nicht Segeln
    Segeln
smöglichkeiten modelliert. Das könnte man dann zum Beispiel auch mit Klassen für die einzelnen Knoten in dem Baum machen. Dein Beispiel hätte dann drei Knoten in der ersten Ebene und beim zweiten Strand noch zwei weitere Auswahlmöglichkeiten.

Code: Alles auswählen

Attraktion
Strand 1
Strand 2
    nicht Segeln
    Segeln
mcdwerner
User
Beiträge: 113
Registriert: Donnerstag 7. Juli 2011, 14:27

Hallo Blackjack!

Die Funktionen sollen nach Zufall entscheiden, ob etwas im Urlaub gemacht wird und wenn ja, wie oft.
Die Zahlen, die den Funktionen übergeben werden sollen die Entscheidungen eingrenzen: z.B. Anzahl von - bis

Das Skript soll mehr eine Programmierübung sein als eine ernsthafte, schnelle Umsetzung.
Das Ergebnis soll eine Liste mit Vorschlägen sein, wie man den Urlaub verbringen könnte.
Dinge wie: wie teile ich die vorhandene Urlaubs-Zeit auf die Aktionen auf, wie gebe ich die Liste aus etc sind in diesem Skript momentan nicht enthalten, sollen aber noch implementiert werden.

Dein Hinweis, dass meine Funktionen eigentlich Daten kodieren schwirrt mir auch schon die ganze Zeit im Kopf rum! Daher die Idee Klassen daraus zu machen. ( -> grundsätzlich richtiger Denkansatz???)

Mit Programmierung und Python beschäftige ich mich in meiner Freizeit seit ca 4 Wochen, darum sieht das ganze vielleicht nach (bayrisch) "ebbs und nix" aus (hochdeutsch evtl. "weder Fisch noch Fleisch"?).
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

mcdwerner hat geschrieben: Das Ergebnis soll eine Liste mit Vorschlägen sein, wie man den Urlaub verbringen könnte.
Dinge wie: wie teile ich die vorhandene Urlaubs-Zeit auf die Aktionen auf, wie gebe ich die Liste aus etc sind in diesem Skript momentan nicht enthalten, sollen aber noch implementiert werden.
Genau hier sehe ich aber den Denkfehler. Du musst Dir doch eigentlich erst überlegen, aus welchen Daten Du welche Daten erzeugst! Wer bestimmt denn, wie das Minimum und das Maximum für einen Strandbesuch aussehen? Was sind eigentlich Strand1 und Strand2? BlackJack hatte ja einige Vermutungen angestellt; in diese Richtung musst Du jetzt mal denken und versuchen uns den Plan / die Idee präzise zu schildern.

Vorher kann man auch wenig zur Benutzung von Klassen sagen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mcdwerner
User
Beiträge: 113
Registriert: Donnerstag 7. Juli 2011, 14:27

Diese Funktion hier kriegt entweder 0,1 oder 2 übergeben:
Bei 0 wird die einzigartige Attraktion auf keinen Fall besucht, bei 1 auf jeden Fall und bei 2 lassen wir den Zufall entscheiden

Code: Alles auswählen

def einzigartigeAttraktion(n):
#übernimmt 0, 1 oder 2
    Auswahl = n
    if Auswahl == 2:
        Auswahl = random.randint(0,1)
    if Auswahl:
        return 'Yippie, wir besuchen die einzigartige Attraktion.'
    else:
        return 'einzigartige Attraktion: Nö!'
Ähnlich hier:

Code: Alles auswählen

def Strand1(von, bis):
#übernimmt 2 Argumente: Zufallsauswahl von bis...       
    von = von
    bis = bis
    if bis == 0:
        strand = 0
    else:
        strand = random.randint(0,1)
    if strand:
        strandreturn = 'Wir gehen ' + str(random.randint(von, bis)) + ' mal an Strand 1.'
    else:
        strandreturn = 'Strand 1: Nö!'
    return strandreturn
wenn das zweite Argument "bis" == 0 haben wir uns entschieden diesen Strand nicht zu besuchen ansonsten soll der Zufall entscheiden, wie oft wir hingehen, eingegrenzt zwischen von - bis

Wenn ich das ganze Skript folgendermaßen laufen lasse:

print(einzigartigeAttraktion(2), '\n', Strand1(5, 10), '\n', Strand2('Segeln', 7))

erhalte ich folgendes zufällige Ergebnis:

Yippie, wir besuchen die einzigartige Attraktion.
Strand 1: Nö!
Wir gehen 2 mal an Strand 2, davon 0 mal Segeln.

Und schon sind alle Diskussionen beendet (zumindest in der Familie ;-)

Ich könnte natürlich so ähnliche Funktionen für jede "Attraktion" schreiben (Museum 1-4, Restaurant 1-8 etc....) aber genau das sollten Klassen nach meinem Verständnis vereinfachen:
Ich schreibe je eine Klasse Strände, Museen, Restaurants, Attraktionen etc erstelle zu jedem Museum, Rastaurant eine entsprechende Instanz der Klasse und schon kann ich den ganzen Urlaub verplanen und wenn's nicht schön ist, dann ist der Zufall schuld...
Ich könnte natürlich auch eine Liste von Zeitpunkten (1. Tag Mittag, 1. Tag Nachmittag etc....) erzeugen und jedem Listenpunkt eine bestimmte Aktion zuteilen aber ich möchte wetterunabhängig sein und deshalb reicht mir eine Liste in der z.B. steht "Wir gehen 2 mal an Strand 2, davon 0 mal Segeln."

Hoffe es ist jetzt verständlicher?
Wie gesagt, es geht mir weniger um die konkrete Urlaubs-Umsetzung, mehr um die Programmierübung.

Edith sagt:

Wenn die Anzahl der Aktionen die das Programm ausspukt (z.B: 48) nicht zur vorhandenen Zeit passt (z.b: 35) dann wird einfach nach Verhältnis umgerechnet (grob gesagt: jedes einzelne Ergebnis mal 35 geteilt durch 48)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Die Frage ist doch, inwiefern sich "Aktionen" im Urlaub von einander unterscheiden bei Deiner "Planung". Das ist die Kunst der Abstraktion. Natürlich kann man loslegen und alles in Klassen verpacken - aber was gewinnst Du dadurch? Du musst zuerst ein Verständnis dafür bekommen, welche Daten und welche Operationen für die Modellierung Deines Problems benötigt werden.

Löse Dich doch mal vom Code und überlege Dir erst einmal genau, was Du für verschiedene "Aktionen" im Urlaub hast und was der Computer per Zufall jeweils bei diesen "berechnen" soll. Dann kannst Du anfangen zu überprüfen, ob und inwiefern sich diese Aktionen unterscheiden. Evtl. gelangt man dann zu einem Punkt, wo sich Klassen und spezialisierte Unterklassen prima für die Modellierung nutzen lassen. Im Moment zweifel ich noch daran bei diesem Problem.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
problembär

Geh' doch einfach mal an den Strand. ;)
BlackJack

@problembär: Mit oder ohne segeln? :-)
mcdwerner
User
Beiträge: 113
Registriert: Donnerstag 7. Juli 2011, 14:27

OK, let's try! Wir brauchen:

Klasse "einzigartige Attraktion, Sehenswürdigkeit", Instanzen z.B.: Colloseum, ein bestimmtes Museum etc...:
muss nur max 1x besucht werden, jede Instanz sollte folgende Wahlmöglichkeiten haben: auf jeden Fall besuchen, auf keinen Fall besuchen, Zufall entscheiden lassen
evtl eine "Schnittstelle" zu anderen Instanzen: wenn A schon besucht wird soll B auf keinen/jeden Fall besucht werden

Oberklasse "Attraktionen die mehrfach besucht werden sollen"
Mehrfachbesuch-Unterklasse "Restaurants":
jede Instanz sollte folgende Wahlmöglichkeiten haben: auf jeden Fall besuchen, auf keinen Fall besuchen, Zufall entscheiden lassen ob besucht wird. Wenn sie besucht werden soll: per Zufall entscheiden lassen, wie oft. Wobei ein gewisser Rahmen vorgegeben wird (z.B. mindestens 1x und höchstens 6x besuchen)
Am Ende kann die Zahl der Restaurantbesuche mit dem Urlaubsplan abgestimmt werden (nach dem Verhältnisprinzip)
evtl eine "Schnittstelle" zu anderen Instanzen: wenn A schon besucht wird soll B auf keinen/jeden Fall besucht werden

Mehrfachbesuch-Unterklasse "Freizeit", Instanzen z.B. versch Strände, Berge etc... :
jede Instanz sollte folgende Wahlmöglichkeiten haben: auf jeden Fall besuchen, auf keinen Fall besuchen, Zufall entscheiden lassen ob besucht wird. Wenn sie besucht werden soll: per Zufall entscheiden lassen, wie oft. Wobei ein gewisser Rahmen vorgegeben wird (z.B. mindestens 1x und höchstens 6x besuchen)
Am Ende kann die Zahl der Strandbesuche mit dem Urlaubsplan abgestimmt werden (nach dem Verhältnisprinzip)
evtl eine "Schnittstelle" zu anderen Instanzen: wenn A schon besucht wird soll B auf keinen/jeden Fall besucht werden

Klasse "kann nur besucht werden wenn auch gleichzeitig was anderes besucht wird"
im Beispiel "Segeln kann nur besucht werden wenn Strand2 besucht wird"
evtl über die "Schnittstelle" handeln???
auch hier wieder die Wahlmöglichkeiten: auf jeden Fall besuchen (wenn möglich), auf keinen Fall besuchen, Zufall entscheiden lassen ob besucht wird. Wenn sie besucht werden soll: per Zufall entscheiden lassen, wie oft. Wobei ein gewisser Rahmen vorgegeben wird (z.B. mindestens 1x und höchstens so oft besuchen, wie die Instanz von der der Besuch abhängt)

Als Eingabe:
die verschiedenen Attraktionen mit den zugehörigen Bedingungen und evtl Abhängigkeiten

Ausgabe:
die verschiedenen Attraktionen und die (per Zufall errechneten) Besuchshäufigkeiten evtl mit den Abhängigkeiten

mit einfachen Mitteln möglich oder eher nicht?

@ Problembär:
mir ist klar, dass das ganze ein wenig realitätfremd ist! Hab gehofft das macht's einfacher. Ist offensichtlich nicht so...
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du bist immer noch einen Schritt zu schnell!

Alleine wenn Du Dir Deine beiden Modelle "Restaurants" und "Freizeit" anguckst, unterscheiden die sich nicht die Bohne - außer von der Begrifflichkeit! Und Dein Modell für die "einzigartigen" Dinge ist streng genommen eher ein Spezialfall der beiden anderen und benötigt also keinen speziellen Typen.

Du musst Dich hier imho noch mal von Klassen lösen und auf einem höherem Niveau abstrahieren.

Gerade solche Dinge wie "wenn eine Sache besucht wird, darf eine bestimmte andere nicht mehr besucht werden" ist alles andere als trivial. Einfacher wäre es, wenn Du solche Aktionen von vorherein in eine "one-of"-Relation packst. Du siehst, vieles ist einfach eine Frage der Modellierung.

Bevor Du Dich also um "Klassen"-Modelle kümmerst, musst Du Dir erst einmal klar werden, wie Du die Daten repräsentierst. Benötigst Du eine spezielle Logik? Kann man das über Graphen lösen? Wie definiert man die Reihenfolge? Gibt es andere Prinzipien, auf die man das runterbrechen kann?

Wenn man mal "Abhängigkeiten" außen vor lässt, würde ich erst einmal ganz banal mit Tupeln arbeiten:

Code: Alles auswählen

activities = (
    ("Strand", 3, 6),
    ("Colosseum", 1, 1),
    ("Freizeitpark", 0, 1),
    ("Sixtinische Kapelle", 0, 0)
)

def create_schedule(activities):
    result = []
    for name, min_, max_ in activities:
        result.extend([name for _ in xrange(0, randint(min_, max_))])
    return result
Wie Du siehst ist das ein ganz einfaches Modell - kommt aber ohne Klassen aus. Immerhin kann ich eine Aktivität immer einplanen, gar nicht oder eben mit beliebiger unterer und oberer Schranke. (Wobei ich gar nicht ziemlich sinnlos finde - da muss ich diesen Freizeitaspekt auch erst gar nicht angeben :-D )

Will man noch Aktivitäten unterbringen, so dass man die nur dann erledigen kann, wenn man schon etwas anderes gemacht hat, so muss man sich wohl ein Graphenmodell ausdenken. Am einfachsten wäre es, wenn Dinge nur von einer Sache abhängen, dann reicht ein Baum. Können Sachen von mehreren Dingen abhängen, wird es komplexer, da man den Graphen anders traversieren muss.

So langsam denke ich, dass das ganze ein wenig an einen Schaltkreis mit den Standard-Logik-Elementen erinnert. Evtl. ist das ein Ansatzpunkt, komplexere Dinge zu modellieren.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mcdwerner
User
Beiträge: 113
Registriert: Donnerstag 7. Juli 2011, 14:27

Oh Mann! Danke Hyperion!!!!
Da gibt's noch viel zu lernen für einen Einsteiger.
Und ich dachte noch "Das ist sicher ein gutes Beispiel um endlich mal die 'Klassenlogik' zu kapieren"...
Habt ihr evtl ein paar Beispiele abseits der Schiene: Mensch - Schulmitglied - Lehrer/Schüler/Professor um in die Verwendung von Klassen einzusteigen?
Piet Lotus
User
Beiträge: 80
Registriert: Dienstag 14. November 2006, 10:40

Hallo mcdwerner,
auch wenn der Thread schon lang ist, hier vielleicht noch mal eine allgemeinere Anmerkung zu OOP. Allgemein kann man sagen, dass Attribute und Methoden, die sich verschiedene Objekte teilen können in eine Oberklasse "geschoben" werden können. Diese verschiedenen Objekte können durch Vererbung diese Attribute und Methoden erben und man spart sich so eine Menge ("Tipp-")Arbeit. Die abgeleiteten Klassen, d.h. die Klassen, die Erben können dann die geerbten Attribut und Methoden noch um eigene spezifische Daten und Methoden erweitern. Man braucht nicht von einer Klasse erben, wenn man mit den zur Verfügung stehenden Attributen und Methoden auskommt. Dann bildet man "nur" eine Instanz der Klasse. Das ist nun sehr theoretisch. In deinem Beispiel könnte man sich eine (Ober-)klasse "Attraktionen" denken, mit den Attributen "name","von","bis","ort","entfernung","eintrittspreis", usw. Du könntest jetzt direkt die Instanzen Strand1, Strand2 usw. bilden. Oder wenn du z.B. Fallschirmspringen willst, bräuchtest du vielleicht das Attribut "mut_erfordert". Dann könntest du von "Attraktionen" den "name","von",usw, erben und ergänzt einfach das Attribut "mut_erfordert". In Bezug auf dein Beispiel Abseits der Schiene, könnte man annehmen, dass es eine Klasse "Mensch" mit den Attributen "name","größe","gewicht","alter","geschlecht" gibt, die für alle Lehrer/Schüler/Professoren gebraucht werden könnten. Wenn du mit diesen Attributen auskommst,dann bildest du halt nur Instanzen. Muss der Professor aber noch "prüfungen_abnehmen" oder der Schüler "schulpflichtig" sein, dann kannst du die Klasse "Mensch" als Oberklasse betrachten und Schüler und Professor durch Vererbung spezifisch erweitern.
Hoffe, das hilft und verwirrt nicht...
Viele Grüße
Piet
Antworten